2046 Commits
0.1.0 ... main

Author SHA1 Message Date
MartinBraquet
5b6c30b987 Debounce search filter updates to improve input handling and performance 2026-04-04 16:27:41 +02:00
MartinBraquet
8da9bd8883 Add validateProfileFields for LLM profile field normalization and filtering 2026-04-04 16:10:17 +02:00
MartinBraquet
bdbce67423 Add type="button" to theme toggle for accessibility compliance 2026-04-04 14:37:55 +02:00
MartinBraquet
e2cdfc01cd Remove unnecessary console logs and update Playwright reporter configuration 2026-04-04 14:34:50 +02:00
Okechi Jones-Williams
d2c9d12b39 test(e2e): add auth, settings, social and organization page coverage
* Added Database checks to the onboarding flow

* Added compatibility page setup
Added more compatibility questions

* Finished up the onboarding flow suite
Added compatibility question tests and verifications
Updated tests to cover Keywords and Headline changes recently made
Updated tests to cover all of the big5 personality traits

* .

* Fix: Merge conflict

* .

* Fix: Added fix for None discriptive error issue #36
Updated signUp.spec.ts to use new fixture
Updated Account information variable names
Deleted "deleteUserFixture.ts" as it was incorporated into the "base.ts" file

* Linting and Prettier

* Minor cleaning

* Organizing helper func

* Added Google account to the Onboarding flow

* .

* Added account cleanup for google accounts

* Started work on Sign-in tests
Updated seedDatabase.ts to throw an error if the user already exists, to also add display names and usernames so they seedUser func acts like a normal basic user
Some organising of the google auth code

* Linting and Prettier

* Added checks to the deleteUser func to check if the accout exists
Added account deletion checks

* Linting and Prettier

* Added POM's for social and organisation page
Updated settings POM

* Formatting update, fixed homePage locator for signin

* .

* .

* .

* Coderabbitai fix's

* Fix

* Improve test utilities and stabilize onboarding flow tests

* Changes requested

* Seperated deletion tests from onboarding

* Update `.coderabbit.yaml` with improved internationalization guidance and formatting adjustments

* Clean up `.vscode/settings.json` and add it to `.gitignore`

* Add Playwright E2E test guidelines to `.coderabbit.yaml`

* Standardize and improve formatting in `TESTING.md` for better readability and consistency.

* Refactor onboarding flow tests and related utilities; improve formatting and remove redundant tests.

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2026-04-04 14:21:40 +02:00
MartinBraquet
09736cd49b Display toast notifications for push messages with navigation fixes 2026-04-03 18:58:36 +02:00
MartinBraquet
f16bef97dc Update styles in SkipLink component to use bg-canvas-100 instead of bg-primary-500 2026-04-03 18:56:32 +02:00
MartinBraquet
92d4222f96 Add debug key 2026-04-03 18:28:19 +02:00
MartinBraquet
008110b015 Refactor Sentry tags for Android app info 2026-04-03 17:25:26 +02:00
MartinBraquet
29ace2d2e5 Release 2026-04-03 13:54:45 +02:00
MartinBraquet
07f927d738 Merge remote-tracking branch 'origin/main' 2026-04-03 13:53:56 +02:00
MartinBraquet
993117ba72 Integrate Sentry for Android app version and build tracking 2026-04-03 13:53:43 +02:00
Martin Braquet
c8801a0235 Enhance CodeRabbit configuration settings
Updated CodeRabbit configuration with new settings for reviews, chat auto-reply, and pre-merge checks.
2026-04-02 19:09:13 +02:00
Martin Braquet
93cd105871 Add .coderabbitignore to exclude specific files 2026-04-02 19:02:54 +02:00
Martin Braquet
4f98d99dd9 Update .coderabbit.yaml configuration
Removed auto-generated PR summary settings and path filters.
2026-04-02 19:02:40 +02:00
Martin Braquet
b46d39d9b7 Enable automatic reviews in CodeRabbit configuration 2026-04-02 18:53:34 +02:00
Martin Braquet
5ea095662b Add CodeRabbit configuration for PR reviews
This configuration file sets up CodeRabbit's behavior for PR summaries and reviews, including instructions for summarizing changes, review profiles, and file exclusions.
2026-04-02 18:53:00 +02:00
MartinBraquet
2400d50247 Add www.compassmeet.com to allowed remotePatterns in next.config.ts 2026-04-02 15:50:30 +02:00
MartinBraquet
8ffd69ff15 Bump Android version code to 81 2026-04-02 15:48:52 +02:00
MartinBraquet
0b721ec7b9 Update alt text in PhotosModal and add WEB_URL clarification comment 2026-04-02 15:48:26 +02:00
MartinBraquet
2019c835a0 Release 2026-04-02 15:21:21 +02:00
MartinBraquet
ff23a8c1bc Wrap useProfile with ProfileProvider and refactor to use React Context 2026-04-02 15:00:48 +02:00
MartinBraquet
df775e9aa3 Improve error handling in run by preserving additional error details 2026-04-02 14:07:02 +02:00
MartinBraquet
d121c92708 Release 2026-04-02 13:44:04 +02:00
MartinBraquet
2db74bf256 Update build scripts, constants, and Android configurations for improved web view handling and debugging
- Rename `proxy.ts` to `_proxy.ts` temporarily during build scripts and clean up backup files.
- Adjust Compass URLs to consistently include "www."
- Introduce WebView debugging toggle via `BuildConfig.ENABLE_WEBVIEW_DEBUG`.
- Bump Android version code to 79 and enable `buildConfig` features.
2026-04-02 13:38:37 +02:00
MartinBraquet
0d758eb5b1 Update Compass URLs to include "www" and improve deep link handling 2026-04-02 13:10:28 +02:00
MartinBraquet
223387129e Rename middleware function to proxy in middleware.ts for clarity 2026-04-02 12:39:21 +02:00
MartinBraquet
01944f9a73 Rename middleware function to proxy in middleware.ts for clarity 2026-04-02 12:38:33 +02:00
MartinBraquet
f9a1dce7b5 Migrate apex redirect logic from next.config.ts to middleware for improved flexibility and maintainability 2026-04-02 12:31:11 +02:00
MartinBraquet
98271784b0 Uncomment redirect rule for compassmeet.com to www.compassmeet.com 2026-04-02 12:18:15 +02:00
MartinBraquet
2311fcbf90 Remove redundant "www" from Compass URLs across codebase 2026-04-02 10:35:07 +02:00
MartinBraquet
f6fef171fa Enhance deep link handling for Android with pending link consumption and JavaScript interface 2026-04-02 10:31:43 +02:00
MartinBraquet
7565d373e9 Add deep link handling for Android app 2026-04-02 09:23:52 +02:00
MartinBraquet
c4fb694c71 Handle deep links in Android MainActivity 2026-04-02 09:23:29 +02:00
MartinBraquet
bdf8793d4a Fix 2026-04-01 22:04:57 +02:00
MartinBraquet
05bd25c9ab Fix 2026-04-01 21:53:19 +02:00
MartinBraquet
5dc98fcbcf Fix 2026-04-01 21:49:03 +02:00
MartinBraquet
91f11161a2 Fix 2026-04-01 21:43:27 +02:00
MartinBraquet
0c60e8a865 Add rewrites and redirects for .well-known/assetlinks.json and compassmeet.com domain 2026-04-01 21:35:36 +02:00
MartinBraquet
3c8566a9e8 Update assetlinks.json to add new SHA-256 certificate fingerprint 2026-04-01 21:21:16 +02:00
MartinBraquet
15f0dd5aaf Add assetlinks.json to enable Android app link verification 2026-04-01 21:17:36 +02:00
MartinBraquet
fc199a918a Fix logs not showing in google cloud console 2026-04-01 18:56:41 +02:00
MartinBraquet
b936466a9d Update stats page SEO metadata for improved clarity and engagement 2026-04-01 18:17:27 +02:00
MartinBraquet
781b5ec674 Comment out og:image:width and og:image:height meta tags to align with updated image metadata usage. 2026-04-01 18:10:15 +02:00
MartinBraquet
a3dfcb4080 Fix 2026-04-01 17:56:29 +02:00
MartinBraquet
0fe1ffe78e Update metadata to use static favicon paths and add image type definition 2026-04-01 17:41:55 +02:00
MartinBraquet
e280b1f5e0 Standardize logging by replacing console.debug with dynamic log levels in log.ts module 2026-04-01 16:53:50 +02:00
MartinBraquet
cc43a8f8af Replace console.debug with log for standardized logging and clean up unused imports in get-profiles API module 2026-04-01 16:53:27 +02:00
MartinBraquet
3caf56247f Refactor profile-about component structure to improve flexibility and simplify render logic, add children prop for custom content support. 2026-04-01 15:25:56 +02:00
MartinBraquet
032c70e086 Remove tooltip from switch-setting component to simplify UI 2026-04-01 15:25:48 +02:00
MartinBraquet
903679eaa7 Refine button styling by adding w-fit for better width handling 2026-04-01 15:25:33 +02:00
MartinBraquet
2908a1f16d Improve hover state styling for measurement system toggle 2026-04-01 15:25:23 +02:00
MartinBraquet
b5deefbec1 Enforce text length limits in editor paste handling, adjust API request size limit, and crop excessive content in LLM profile extraction 2026-04-01 13:52:34 +02:00
MartinBraquet
0cb6226643 Refine theme handling with dark mode support and update UI styles accordingly 2026-03-31 15:10:37 +02:00
MartinBraquet
b19f6b81de Fix French translation for "Psychédéliques" label in profile settings 2026-03-31 15:05:25 +02:00
MartinBraquet
401fd816dd Bump version numbers in package.json and Android build.gradle 2026-03-31 13:41:51 +02:00
MartinBraquet
a31765b7ae Refactor profile and form submission logic to support partial updates and improve type handling 2026-03-31 13:41:24 +02:00
MartinBraquet
a7ae62a14b Clarify usage frequency descriptions for psychedelics and cannabis in profile data 2026-03-31 13:40:59 +02:00
MartinBraquet
007bc1f7b0 Refine animated ring behavior to cancel on drag and improve pointer handling 2026-03-31 13:40:47 +02:00
MartinBraquet
bded8cc1fe Add additional Sentry error capture and refine profile data update logic 2026-03-30 23:14:24 +02:00
MartinBraquet
cfaac3e3fa Add UI dynamics 2026-03-30 22:45:11 +02:00
MartinBraquet
9637c80dd7 Add Sentry error handling 2026-03-30 20:15:46 +02:00
MartinBraquet
6a9739ab31 Update Sentry context key to 'Error Info' in profile page 2026-03-30 17:05:33 +02:00
MartinBraquet
a51bd344a2 Fix 2026-03-30 17:05:22 +02:00
MartinBraquet
50f9a00689 Add info more 2026-03-30 16:58:45 +02:00
MartinBraquet
54fb8c4b61 Add info 2026-03-30 16:52:12 +02:00
MartinBraquet
809a996870 Add message 2026-03-30 16:38:43 +02:00
MartinBraquet
85c96ce430 Add more sentry info 2026-03-30 16:33:29 +02:00
MartinBraquet
b670de9c73 Add sentry error capture 2026-03-30 16:25:16 +02:00
MartinBraquet
626b28f4eb Release 2026-03-30 15:47:59 +02:00
MartinBraquet
9c3dd65fc9 Add missing file 2026-03-30 15:03:18 +02:00
MartinBraquet
1e5c7b07c2 Add substance fields: cannabis and psychedelics 2026-03-30 14:55:31 +02:00
MartinBraquet
686c5777fd Seed more profiles 2026-03-30 13:27:43 +02:00
MartinBraquet
611e07a02b Fix div element parsing 2026-03-29 23:05:58 +02:00
MartinBraquet
3c514de79d Fix locale not passed 2026-03-29 21:03:26 +02:00
MartinBraquet
8319d21dea Add translation 2026-03-29 20:36:12 +02:00
MartinBraquet
e4a231b0c5 Release 2026-03-29 20:29:48 +02:00
MartinBraquet
b1efd042cf Fix wrong text editor picked 2026-03-29 20:29:17 +02:00
MartinBraquet
8a7d2120c4 Fix null values 2026-03-29 20:21:31 +02:00
MartinBraquet
e0b3f2d81a Handle null site 2026-03-29 20:05:10 +02:00
MartinBraquet
9f1eaef30c Fix null linknder 2026-03-29 19:55:54 +02:00
MartinBraquet
0218f4e705 Fix null link 2026-03-29 19:55:35 +02:00
MartinBraquet
3171f32cec Fix links and add gemini models 2026-03-29 19:30:51 +02:00
MartinBraquet
156d2870cb Fix height 2026-03-29 18:52:38 +02:00
MartinBraquet
2229d01aa1 Fix test 2026-03-29 18:31:41 +02:00
MartinBraquet
6e4c6f29b5 Add profile auti-fill from content or URL 2026-03-29 18:26:45 +02:00
MartinBraquet
ad51aea069 Fix faq bold 2026-03-27 14:52:02 +01:00
MartinBraquet
b8963c99a7 Add big 5 info 2026-03-23 10:26:42 +01:00
MartinBraquet
b270ce706b Fix button flash 2026-03-21 21:18:35 +01:00
MartinBraquet
2c04b2a6aa Add user dependency to refreshPinnedQuestionIds in PinnedQuestionIdsProvider 2026-03-21 21:08:07 +01:00
MartinBraquet
a00b966e8b Simplify null checks for user and update sign-up text (fix #39) 2026-03-21 21:07:43 +01:00
MartinBraquet
8ac3259f41 Add quotation marks to profile headline and capitalize suffix in profile details 2026-03-20 21:05:06 +01:00
MartinBraquet
56f0423d5a Enable Sentry only for non-local environments 2026-03-19 17:29:09 +01:00
MartinBraquet
e83ec6506c Fix API error handling to correctly parse error details 2026-03-19 17:16:02 +01:00
MartinBraquet
be22658883 Release 2026-03-19 17:04:18 +01:00
MartinBraquet
bdafa43472 Add pinned compatibility questions feature with backend support and UI integration 2026-03-19 17:00:34 +01:00
MartinBraquet
891b91d0ba Clean comment 2026-03-18 10:55:08 +01:00
MartinBraquet
b85b9380c3 Fix icon alignment 2026-03-17 13:33:41 +01:00
Okechi Jones-Williams
cfeab0278c [Housekeeping] Organizing the test structure and files, adding fix for issue #36 (None descriptive error) (#38)
* Added Database checks to the onboarding flow

* Added compatibility page setup
Added more compatibility questions

* Finished up the onboarding flow suite
Added compatibility question tests and verifications
Updated tests to cover Keywords and Headline changes recently made
Updated tests to cover all of the big5 personality traits

* Fix: Added 'tsconfig-paths/register' to playwright config so it applies tsconfig/ts-node to test files and imported modules....there was a syntax error:

SyntaxError: tests/e2e/web/pages/homePage.ts: Unexpected token (2:0)

* .

* Updated ProfilePage: In some cases removed the use of "one shot grabs" using .textContent() to prevent flaky tests
Added test for entering profile information post signup flow

* Linting and Prettier

* Fix: Merge conflict

* Fix: Modified sortedAndFilteredAnswers to use "UseMemo" so that it doesnt run every time something changes

* .

* .

* Merged "verifyInterestedInConnectingWith" and "verifyRelationShipTypeAndInterest" into "verifySeeking" to match ui changes

* Added error message outlining the minimum character requirement for display names and usernames

* Updated displayName error message to show when editing the user profile post signup

* Fix: Added fix for None discriptive error issue #36
Updated signUp.spec.ts to use new fixture
Updated Account information variable names
Deleted "deleteUserFixture.ts" as it was incorporated into the "base.ts" file

* Linting and Prettier

* Minor cleaning

* Organizing helper func

* Linting and Prettier

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2026-03-16 16:02:48 +01:00
MartinBraquet
c8735b8b01 Comment 2026-03-15 17:52:03 +01:00
MartinBraquet
5ecbd3ba91 Fix cached user key 2026-03-15 17:41:48 +01:00
MartinBraquet
f83dbf349e Fix API error details and add toast info 2026-03-15 14:31:42 +01:00
MartinBraquet
3d1e91d100 Fix search input value 2026-03-15 14:04:09 +01:00
MartinBraquet
0353a530d4 Release 2026-03-15 13:42:39 +01:00
MartinBraquet
0592c7e766 Add support for mp4 media in profiles 2026-03-15 13:42:20 +01:00
MartinBraquet
5a8c698ed5 Remove redundant FilterGuide component 2026-03-14 13:55:13 +01:00
MartinBraquet
7ff38bd693 Release 2026-03-14 13:48:08 +01:00
MartinBraquet
6c84926033 Release 2026-03-14 13:11:00 +01:00
MartinBraquet
f35af89f07 Release 2026-03-13 16:04:05 +01:00
MartinBraquet
b1f01fd873 Fix wording 2026-03-13 16:00:42 +01:00
MartinBraquet
96e22136a4 Fix global choices not refreshing after profile creation / edition 2026-03-13 15:56:43 +01:00
MartinBraquet
f81932e14e Remove one skip 2026-03-13 15:55:55 +01:00
MartinBraquet
6b1813e129 Close modal before validating questions 2026-03-13 15:32:18 +01:00
MartinBraquet
307076d88e Update 2026-03-13 15:18:38 +01:00
MartinBraquet
a84ad62ea2 Fix debug 2026-03-13 15:02:54 +01:00
MartinBraquet
c3f21058e5 Fix stable questions 2026-03-13 14:54:59 +01:00
MartinBraquet
d3634d8b1c Move interestChoices to app context to fix db blast 2026-03-13 14:35:20 +01:00
MartinBraquet
cdbba244d0 Comment 2026-03-13 14:34:09 +01:00
MartinBraquet
6daeea908e Allow to set debug level in env var 2026-03-13 14:33:41 +01:00
MartinBraquet
b835a5f137 Add debug 2026-03-13 14:33:11 +01:00
MartinBraquet
25358a9463 Update Sentry project name 2026-03-13 13:04:06 +01:00
MartinBraquet
11063611bf Do not ship source maps even in web 2026-03-13 12:49:12 +01:00
MartinBraquet
6a03c5cc83 Clean 2026-03-13 12:43:56 +01:00
MartinBraquet
37ddf5bab1 Clean up unused instance ID env var 2026-03-13 12:40:08 +01:00
MartinBraquet
f741648522 Stabilize questions order 2026-03-13 00:47:31 +01:00
MartinBraquet
57e6395641 Release 2026-03-12 22:15:20 +01:00
MartinBraquet
bc31df7d0a Use colored fav icon 2026-03-12 12:33:44 +01:00
MartinBraquet
1e4b836985 Release 2026-03-12 10:59:29 +01:00
MartinBraquet
5c1b18b4d9 Fix gap 2026-03-12 01:17:57 +01:00
MartinBraquet
f90b2179b5 Add gender translations 2026-03-12 01:15:02 +01:00
MartinBraquet
0da158ce54 Update e2e test 2026-03-12 00:58:56 +01:00
MartinBraquet
a360b51e12 Update translations 2026-03-12 00:49:14 +01:00
MartinBraquet
3de6adae2e Refine cards and allow users to toggle each card field ON or OFF 2026-03-12 00:36:36 +01:00
MartinBraquet
01a6a6e298 Fix emoji list showing on scrolling 2026-03-11 18:08:36 +01:00
MartinBraquet
54ce5891f6 Add card size selector 2026-03-11 17:40:28 +01:00
MartinBraquet
8a2bcad190 Fix tooltip not open on mobile click 2026-03-11 12:24:28 +01:00
MartinBraquet
a88ba2dd3c Add comments 2026-03-11 12:12:36 +01:00
MartinBraquet
3503110c64 Do not show cancelled events 2026-03-11 12:12:21 +01:00
MartinBraquet
33436c84a4 Imporve wording 2026-03-10 23:52:43 +01:00
MartinBraquet
52f0f04194 Fix user not logging in when they accidentally sign in through the sign up page 2026-03-10 23:48:55 +01:00
MartinBraquet
3d56bb4fe0 Clear logs 2026-03-10 23:48:14 +01:00
MartinBraquet
b0c84687d2 Min 18 yo 2026-03-10 23:48:04 +01:00
MartinBraquet
0ef5ecea30 Send discord notif upon event creation 2026-03-10 23:32:34 +01:00
MartinBraquet
670e863bae Add SEO 2026-03-10 23:32:12 +01:00
MartinBraquet
3309ed1988 Add 2026-03-10-delete-users-without-profile.ts 2026-03-10 23:13:26 +01:00
MartinBraquet
3365445538 Delete orphan user to prevent onboarding issues 2026-03-10 23:02:47 +01:00
MartinBraquet
9808b4a2e7 Clean display 2026-03-10 20:19:05 +01:00
MartinBraquet
f3bd28e29f Fix 2026-03-10 16:29:48 +01:00
MartinBraquet
da9e950e5f Fix margin 2026-03-10 16:25:33 +01:00
MartinBraquet
dbf12a2ab2 Fix zod types and unseen icon not showing 2026-03-10 16:22:44 +01:00
MartinBraquet
1a2aa16645 Release 2026-03-10 00:50:09 +01:00
MartinBraquet
a1df61edaa Update supabase schema 2026-03-10 00:41:08 +01:00
MartinBraquet
9e58e12013 Encrypt early messages that were sent before AES encryption got implement 2026-03-10 00:39:04 +01:00
MartinBraquet
a71c0beb11 Fix unit test 2026-03-09 22:01:50 +01:00
MartinBraquet
93e6b18b49 Disable next button while uploading photos 2026-03-09 21:57:07 +01:00
MartinBraquet
6aae66f0d2 Translate back 2026-03-09 21:33:09 +01:00
MartinBraquet
46f751b712 Translate 2026-03-09 21:31:36 +01:00
MartinBraquet
ccce2cc8b0 Release 2026-03-09 19:34:37 +01:00
MartinBraquet
c38d752dc8 Fix types 2026-03-09 19:33:49 +01:00
MartinBraquet
6f45c03a29 Show number of answers and community importance on prompts 2026-03-09 19:24:32 +01:00
MartinBraquet
5819f08aec Release 2026-03-09 15:19:03 +01:00
MartinBraquet
a322ea77fc Fix .webp not rending in OG: convert to jpg on upload 2026-03-09 14:58:16 +01:00
MartinBraquet
9a5f47f905 Fix early banner not showing 2026-03-09 14:32:00 +01:00
MartinBraquet
a02ba9767b Fix auto scrolling when receiving a message 2026-03-09 14:21:23 +01:00
MartinBraquet
57edf80bfd Translate creating profile 2026-03-09 14:13:51 +01:00
MartinBraquet
3a2db534ab Round back 2026-03-09 14:06:26 +01:00
MartinBraquet
8eac568446 Do not error log if nav share not present 2026-03-09 14:06:13 +01:00
MartinBraquet
5e5015018f Round xl 2026-03-09 14:05:48 +01:00
MartinBraquet
1fce55aebc Clean 2026-03-09 13:38:25 +01:00
MartinBraquet
cae5b96b1e Fix sorting by answer count 2026-03-09 13:36:52 +01:00
MartinBraquet
3c72bca496 Add typing for /stats 2026-03-09 13:00:24 +01:00
MartinBraquet
ba7e158af8 Release 2026-03-09 12:38:08 +01:00
MartinBraquet
34a13458db Fix unit tests 2026-03-09 12:37:46 +01:00
MartinBraquet
3200e3cf79 Fix index and total not refreshing 2026-03-09 12:12:40 +01:00
MartinBraquet
de9c28965f Fix 2026-03-09 03:18:04 +01:00
MartinBraquet
4e61669361 Add option to sort prompts in modal 2026-03-09 03:16:08 +01:00
MartinBraquet
09607ba7c7 Weigh community importance score by answer count to avoid low sample bias 2026-03-09 03:15:21 +01:00
MartinBraquet
24d2fe9c32 Sort compat prompts by community importance and answer count 2026-03-09 02:27:10 +01:00
MartinBraquet
94585b1f1d Fix constitution wording 2026-03-08 21:24:11 +01:00
MartinBraquet
155406935d Add option to search by keyword in the prompts of a profile page: questions, answers, and free answers. 2026-03-08 18:36:57 +01:00
MartinBraquet
b445db6116 Add profile filter: has photos 2026-03-08 18:11:28 +01:00
MartinBraquet
d4de56873f Add currently online choice 2026-03-08 18:10:27 +01:00
MartinBraquet
6c54a9adf0 Add option to view pics on profile cards 2026-03-08 17:38:43 +01:00
MartinBraquet
74f948e6ca Add gender retio to stats 2026-03-08 16:55:58 +01:00
MartinBraquet
0ea9ee969e Add supporting members 2026-03-08 16:32:36 +01:00
MartinBraquet
6ae1af3c1f Add filters doc 2026-03-08 16:21:26 +01:00
MartinBraquet
4ac4ab0ba2 Update FAQ 2026-03-08 14:21:59 +01:00
MartinBraquet
d29edae5fe Fix no messages yet showing even when loading 2026-03-08 14:11:57 +01:00
MartinBraquet
066a620bd4 Fix tooltips not showing 2026-03-08 14:10:46 +01:00
MartinBraquet
7ad464150b Fix avatar not showing in hidden profiles 2026-03-08 13:39:31 +01:00
MartinBraquet
596e70e031 Fix avatar not showing in saved people 2026-03-08 13:27:41 +01:00
MartinBraquet
13f103a3ca Hide logs in prod 2026-03-08 01:17:00 +01:00
MartinBraquet
a699447e9e Fix color prompts 2026-03-08 00:02:45 +01:00
MartinBraquet
159e634a1a Fix 2026-03-07 16:18:57 +01:00
MartinBraquet
0533fdd2ed Fix 2026-03-07 16:02:44 +01:00
MartinBraquet
5119c458d8 Release 2026-03-07 15:59:58 +01:00
MartinBraquet
d8a39f7101 Clean 2026-03-07 15:59:42 +01:00
MartinBraquet
3a0712c193 Fix messaging pagination and scrolling 2026-03-07 15:40:51 +01:00
MartinBraquet
cb9dd51afc Test 2026-03-07 13:46:35 +01:00
MartinBraquet
89ce1a248e Test 2026-03-07 13:43:53 +01:00
MartinBraquet
ffc717c86b Fix 2026-03-07 13:43:07 +01:00
MartinBraquet
30248fd0be Fix 2026-03-07 13:39:43 +01:00
MartinBraquet
c270e6c3d7 Test no deploy 2026-03-07 13:31:55 +01:00
MartinBraquet
e8bc9cda1d Add comment 2026-03-07 13:31:05 +01:00
MartinBraquet
0bc82a3bcf Fix vercel not deploying even if commit before last has changes 2026-03-07 13:29:35 +01:00
MartinBraquet
a5f7898c37 Release 2026-03-07 13:25:20 +01:00
MartinBraquet
1165927337 Fix get message being called for each convo 2026-03-07 13:21:36 +01:00
MartinBraquet
2d5690cea2 Add message count to stats 2026-03-07 12:42:57 +01:00
MartinBraquet
ace1b2823a Improve API docs 2026-03-07 11:42:58 +01:00
MartinBraquet
a50323cd94 Cache stats 2026-03-07 11:42:31 +01:00
MartinBraquet
008bc11ebf Add /stats 2026-03-07 11:28:24 +01:00
MartinBraquet
3ddf81d935 Invalidate user cache 2026-03-07 11:17:29 +01:00
MartinBraquet
67e95be2d4 Upgrade how to auth in API docs 2026-03-07 11:10:48 +01:00
MartinBraquet
0bb0a394ae Reserve /monitoring 2026-03-07 11:10:13 +01:00
MartinBraquet
cc74945371 Fix custom 404 translations 2026-03-07 09:55:13 +01:00
MartinBraquet
2ea34189a8 Add 2026-03-08-migrate-avatar-url.ts 2026-03-07 00:37:39 +01:00
MartinBraquet
66800d949b Hot fix undefined links 2026-03-07 00:24:22 +01:00
MartinBraquet
2eb80b97d5 Clean error message color 2026-03-07 00:23:45 +01:00
MartinBraquet
f4d8822dbe Fix CI 2026-03-07 00:17:38 +01:00
MartinBraquet
0655266366 Move avatar URL and is-banned to separate columns and social links to profiles table 2026-03-06 23:51:49 +01:00
MartinBraquet
4b58e72607 Use API error handler depending on error code 2026-03-06 15:27:49 +01:00
MartinBraquet
29445a8aa7 Update endpoint docs and API error formatting 2026-03-06 13:11:52 +01:00
MartinBraquet
c4a498227f Add docs 2026-03-06 12:35:00 +01:00
MartinBraquet
295fa1dee4 Release 2026-03-06 03:16:22 +01:00
MartinBraquet
ca582f0134 Add DATABASE docs 2026-03-06 02:13:09 +01:00
MartinBraquet
43abe21e45 Remove api docs dark mode 2026-03-06 02:11:42 +01:00
MartinBraquet
c1df4c1307 Fix sentry source maps (2) 2026-03-06 01:06:50 +01:00
MartinBraquet
73802c9c1d Fix sentry source maps 2026-03-06 01:04:10 +01:00
MartinBraquet
2825ded7c0 Clean 2026-03-06 00:55:36 +01:00
MartinBraquet
69161612f6 Get firebase User even if user/privateUser not set 2026-03-06 00:38:40 +01:00
MartinBraquet
2cc6af1f37 Update readmes 2026-03-06 00:20:34 +01:00
MartinBraquet
7a52f55b05 Improve api() 2026-03-06 00:20:20 +01:00
MartinBraquet
f854476614 Get api call 2026-03-06 00:20:04 +01:00
MartinBraquet
7165553080 Use api 2026-03-06 00:18:57 +01:00
MartinBraquet
fbda1caaf7 Fix e2e tests 2026-03-05 23:49:25 +01:00
MartinBraquet
1c3ed84791 Comment 2026-03-05 23:42:14 +01:00
MartinBraquet
6008a5d3a5 Ignore yarn warnings 2026-03-05 23:41:44 +01:00
MartinBraquet
205354c6c4 Fix CI 2026-03-05 20:23:47 +01:00
MartinBraquet
cb8ef458c2 Fix CI 2026-03-05 19:53:36 +01:00
MartinBraquet
d54f0052df Fix flash 2026-03-05 19:49:32 +01:00
MartinBraquet
d979a81b95 Fix jest tests 2026-03-05 19:32:45 +01:00
MartinBraquet
bf8ce092af Fix 2026-03-05 18:25:31 +01:00
MartinBraquet
c53039d97a Release 2026-03-05 18:18:31 +01:00
MartinBraquet
5f32e5d025 Fix profile not found on some signup 2026-03-05 17:51:25 +01:00
MartinBraquet
b3d203afa2 Fix 2026-03-05 15:57:50 +01:00
MartinBraquet
0379c95f9b Add translations 2026-03-05 15:48:25 +01:00
MartinBraquet
512406837d Update options in same API call along with user creation 2026-03-05 15:40:20 +01:00
MartinBraquet
32e8c8570b Add back buttons 2026-03-05 13:09:44 +01:00
MartinBraquet
6c86de75ec Fix 2026-03-04 17:20:49 +01:00
MartinBraquet
4bc91a5311 Trigger deploy 2026-03-04 16:02:15 +01:00
MartinBraquet
822f9150b8 Release 2026-03-04 15:48:47 +01:00
MartinBraquet
6117e59226 Fix e2e tests 2026-03-04 15:47:56 +01:00
MartinBraquet
bf9d25731c Merge branch 'collapse_registration' 2026-03-04 15:23:31 +01:00
MartinBraquet
8686ac4090 Fix tests libs 2026-03-04 15:23:12 +01:00
MartinBraquet
dcacf98ea3 Collapse the 3 onboarding API calls into 1 2026-03-04 15:22:36 +01:00
MartinBraquet
cd9fcb8176 Prettier 2026-03-04 11:08:35 +01:00
MartinBraquet
d158eadf0d Fix lint and typecheck not failing due to ; instead of && 2026-03-04 11:08:08 +01:00
Okechi Jones-Williams
e115df8e11 [Onboarding Flow] Answer compatibility questions in the test suite for onboarding (#35)
* Fixed Type errors

* Added Database checks to the onboarding flow

* Updated Onboarding flow
Changed type ChildrenExpectation so that it can be used for database verification

* Added compatibility page setup
Added more compatibility questions

* Finished up the onboarding flow suite
Added compatibility question tests and verifications
Updated tests to cover Keywords and Headline changes recently made
Updated tests to cover all of the big5 personality traits

* Apply suggestions from code review

---------

Co-authored-by: Martin Braquet <martin.braquet@gmail.com>
2026-03-04 11:05:56 +01:00
MartinBraquet
140ace55bf Fix 2026-03-02 19:57:31 +01:00
MartinBraquet
7a44f3d23c Fix api deploy not installing deps from shared, emails and common 2026-03-02 19:47:30 +01:00
MartinBraquet
2f38d54ea5 Add auth token 2026-03-02 19:00:39 +01:00
MartinBraquet
01deda29e7 Release 2026-03-02 18:06:27 +01:00
MartinBraquet
b59b0edd4a Add sentry 2026-03-02 18:06:04 +01:00
MartinBraquet
be358d8517 Clean 2026-03-02 17:31:45 +01:00
MartinBraquet
4e3f31dd1c Add debug 2026-03-02 16:40:00 +01:00
MartinBraquet
ab439bd85d Clean 2026-03-02 15:58:39 +01:00
MartinBraquet
aa7e32cb77 Fix wording 2026-03-02 15:58:10 +01:00
MartinBraquet
863fd2c0ae Android release 2026-03-01 23:39:54 +01:00
MartinBraquet
a18d308248 Test 2026-03-01 23:36:42 +01:00
MartinBraquet
bfed23769e Fix 2026-03-01 23:35:41 +01:00
MartinBraquet
54a8f0e59b Log 2026-03-01 23:33:11 +01:00
MartinBraquet
90d25c7152 Test 2026-03-01 23:32:10 +01:00
MartinBraquet
9ccdeb6997 Fix 2026-03-01 23:31:08 +01:00
MartinBraquet
ad1b3e813e Fix 2026-03-01 23:30:10 +01:00
MartinBraquet
50949199f4 Update web 2026-03-01 23:26:55 +01:00
MartinBraquet
2d477e498f Fix resolutions 2026-03-01 23:17:48 +01:00
MartinBraquet
f9f9da63a0 Upgrade zod 2026-03-01 23:08:55 +01:00
MartinBraquet
000daa3021 Add ignore command 2026-03-01 23:08:33 +01:00
MartinBraquet
40c30ede11 Fix symbol rotation 2026-03-01 22:47:02 +01:00
MartinBraquet
54fdf67bcf Fix sidebar not closing 2026-03-01 22:46:49 +01:00
MartinBraquet
1bf9b83693 Fix aria label 2026-03-01 22:27:17 +01:00
MartinBraquet
32e97f9da5 Bump 2026-03-01 22:12:55 +01:00
MartinBraquet
677f8bf207 Reduce apk size by excluding .map assets 2026-03-01 22:09:58 +01:00
MartinBraquet
ab92cf2aa9 Upgrade libs 2026-03-01 21:46:13 +01:00
MartinBraquet
0dff23991a Fix android plugins 2026-03-01 21:13:34 +01:00
MartinBraquet
04af8966b5 Release 2026-03-01 19:48:00 +01:00
MartinBraquet
dd239f7b30 Fix infinite loop in setup 2026-03-01 19:47:36 +01:00
MartinBraquet
d2195d7c16 Ignore .email in lint 2026-03-01 19:39:30 +01:00
MartinBraquet
60269b66a7 Release 2026-03-01 19:39:15 +01:00
MartinBraquet
2cad2fca17 Switch to js 2026-03-01 19:35:58 +01:00
MartinBraquet
a10ae2d253 Clean 2026-03-01 19:14:36 +01:00
MartinBraquet
4a4bee658d Add next config for react email dev server 2026-03-01 19:12:30 +01:00
MartinBraquet
75a689707d Ignore react email 2026-03-01 18:31:32 +01:00
MartinBraquet
6f638a22a3 Install react-email 2026-03-01 18:12:06 +01:00
MartinBraquet
18b63f1eb3 Fix OG card not rendering in dev 2026-03-01 18:00:26 +01:00
MartinBraquet
39689b1bfa Update docs next 2026-03-01 18:00:06 +01:00
MartinBraquet
44bc25f061 Update docs 2026-03-01 17:52:00 +01:00
MartinBraquet
e2d9c06362 Clean error boundary 2026-03-01 17:48:57 +01:00
MartinBraquet
165a7e5663 Update next tsconfig 2026-03-01 17:43:20 +01:00
MartinBraquet
8f83011011 Fix import 2026-03-01 17:42:28 +01:00
MartinBraquet
9924c3debf Fix rm 2026-03-01 17:42:00 +01:00
MartinBraquet
836f8f1bfb Git ignore css compiled 2026-03-01 17:32:03 +01:00
MartinBraquet
fae76195ec Fix css 2026-03-01 17:31:51 +01:00
MartinBraquet
0d8d81e09c Fix typecheck 2026-03-01 17:15:28 +01:00
MartinBraquet
699890a0be Massive Next.js (14->16) and React (18->19) upgrade 2026-03-01 16:55:19 +01:00
MartinBraquet
8c68312597 Clean docs 2026-03-01 06:43:10 +01:00
MartinBraquet
55bb9919f7 Fix coverage ts warnings 2026-03-01 06:00:28 +01:00
MartinBraquet
f8ca4bcbfc Android release 2026-03-01 05:36:27 +01:00
MartinBraquet
7037362b40 Update docs 2026-03-01 05:35:46 +01:00
MartinBraquet
e29bc0ab82 Fix test 2026-03-01 05:10:24 +01:00
MartinBraquet
b3cf542fd5 Rename jest config to ts 2026-03-01 04:57:59 +01:00
MartinBraquet
59ddb4360e Fix es lint 2026-03-01 04:56:21 +01:00
MartinBraquet
4411ef25b0 Re-use node setup in CD workflows 2026-03-01 04:46:12 +01:00
MartinBraquet
0d57760d25 Silence debug in tests 2026-03-01 04:38:02 +01:00
MartinBraquet
77f3b550d0 Fix CI 2026-03-01 04:22:40 +01:00
MartinBraquet
79e0421281 Do not wait upon sign up in dev 2026-03-01 04:20:40 +01:00
MartinBraquet
f54e18feb1 Do not show early banner if signup banner is there 2026-03-01 04:20:24 +01:00
MartinBraquet
18d2c59479 API release 2026-03-01 04:16:39 +01:00
MartinBraquet
33d7308cfa Fix tests 2026-03-01 04:15:26 +01:00
MartinBraquet
579ed6de7c Use logger debug 2026-03-01 04:05:14 +01:00
MartinBraquet
8a1ee5cdca Add skip links for accessibility 2026-03-01 03:33:07 +01:00
MartinBraquet
edaf119d9e Add live region 2026-03-01 03:12:39 +01:00
MartinBraquet
1aad769d93 Add logger 2026-03-01 02:58:56 +01:00
MartinBraquet
b5b2bafc78 Add error boundary 2026-03-01 02:56:48 +01:00
MartinBraquet
8ba8604d83 Add /health to vercel API 2026-03-01 02:55:10 +01:00
MartinBraquet
9fdd21e03a DRY checkout 2026-03-01 02:24:08 +01:00
MartinBraquet
418b2c7e52 Fix 2026-03-01 02:22:22 +01:00
MartinBraquet
49237bbe18 Add monorepo info 2026-03-01 02:16:56 +01:00
MartinBraquet
049fffe27f Cache all node modules 2026-03-01 02:16:46 +01:00
MartinBraquet
8d80245adf Test 2026-03-01 02:11:32 +01:00
MartinBraquet
8d235e89f0 DRY the actions 2026-03-01 02:05:24 +01:00
MartinBraquet
b030dd1a52 Improve naming 2026-03-01 01:48:10 +01:00
MartinBraquet
17faf2fe26 Improve CI cache 2026-03-01 01:48:00 +01:00
Okechi Jones-Williams
b18a6d7ff3 [Onboarding Flow] Added database checks to the onboarding flow (#34)
* Fixed Type errors

* Organizing testing utilities

* Added Database checks to the onboarding flow

* Updated Onboarding flow
Changed type ChildrenExpectation so that it can be used for database verification

* Added compatibility page setup
Added more compatibility questions

* Fix

* .

* Fix: Typo

* Fix: Faker usernames can no longer generate symbols

* Fix: Changed how work area is verified

* .

* .

* Fix: Trying to work in headed mode

* Fix: Change back to headless

* Fix: Added timeout after workArea selection

* .

* Clean e2e

* Improve E2E setup

* Prettier

* Log

* Fix: should pull test account from unique identifier like email, username or id; not the display name

* Source env vars in playwright directly

* Clean e2e data

* Clean test account id to be the same for email and username

* Fix import warning

* Add error handling

* Add log

* Temp remove env load

* Update

* Add logs and safeguards against using remote supabase during e2e tests

* Fix playwright report path in C@

* Remove locale log

* Check if userInformationFromDb loading with name instead of username was the issue

* Remove login log

* Check if initial work area names were the issue

* Ignore if no files found

* Cache Firebase emulators in CI

* Reload env vars in playwright

* It did not break tests...

* Clean verifyWorkArea

* Add caching for node modules in CI

* Add caching for node modules in CI (2)

* Do not raise if emulator not running during db seed

* Do not raise if using firebase emulator

* Fix supabase cache in CI

* Add Cache Playwright browsers in CI

* Fix

* Test cache

* Turn off unused supabase services to speed things up

* Back to good one

* Set CI=true

* api is required for client connection

* Add safeguards for missing supabase env vars

* Remove echo

* Remove supabase cache

---------

Co-authored-by: Martin Braquet <martin.braquet@gmail.com>
2026-03-01 01:25:56 +01:00
MartinBraquet
c69a438d08 Add doc to prevent race conditions in e2e tests 2026-02-28 15:21:19 +01:00
MartinBraquet
309cbe7f2b Use unique user ID for each e2e test to fix race condition
Before, each test would delete all existing users—including the ones still used in concurrent tests
2026-02-28 14:37:43 +01:00
MartinBraquet
c0df0028d3 Skip email send locally 2026-02-28 14:35:40 +01:00
MartinBraquet
4722088fd0 Speed up e2e-dev.sh 2026-02-28 14:35:20 +01:00
MartinBraquet
27c03330c8 Format last active filter 2026-02-28 13:23:47 +01:00
MartinBraquet
740a7cc6f9 Fix 2026-02-28 02:11:24 +01:00
MartinBraquet
53ae605e9d Show mobile filter as soon as desktop one disappears 2026-02-28 02:00:35 +01:00
MartinBraquet
84da8b7ad3 Fix mobile padding 2026-02-28 02:00:04 +01:00
MartinBraquet
8b283cc5ce Fix interest filter width 2026-02-28 01:48:01 +01:00
MartinBraquet
8548b85d03 Fix local storage read (now with ttl attached) 2026-02-28 01:45:12 +01:00
MartinBraquet
fbb10344e1 Clean UI 2026-02-28 01:30:51 +01:00
MartinBraquet
615033547c Hide gender they seek 2026-02-28 01:08:51 +01:00
MartinBraquet
8f854995c5 Fix missing key 2026-02-28 01:07:15 +01:00
MartinBraquet
f8bb15e376 Fix typecheck 2 2026-02-28 01:00:10 +01:00
MartinBraquet
f6a65e875b Show selected filters at the top and allow for removed them one by one 2026-02-28 00:34:11 +01:00
MartinBraquet
74fc6a744e Improve wants kids and has kids filters 2026-02-27 22:42:34 +01:00
MartinBraquet
6920b8293d Add filter sections 2026-02-27 21:34:44 +01:00
MartinBraquet
6c71022ed6 Move desktop filters to right side 2026-02-27 17:19:32 +01:00
MartinBraquet
d0176c2b65 Seed more profiles 2026-02-27 17:17:41 +01:00
MartinBraquet
5ce38fea65 Add missing sql 2026-02-27 17:17:33 +01:00
MartinBraquet
19ee048536 Set to the best guess (first city) if no option selected 2026-02-27 15:09:28 +01:00
MartinBraquet
2531ee6fe4 Add banner to let users know it's early stage 2026-02-27 14:59:59 +01:00
MartinBraquet
1722cb531f Add TTL support to persistent cache hook 2026-02-27 14:59:39 +01:00
MartinBraquet
f59325cbed Clean 2026-02-27 13:39:19 +01:00
MartinBraquet
1c595d3e33 Move DropdownOptions 2026-02-27 13:26:33 +01:00
MartinBraquet
4f2df43232 Fix wording 2026-02-27 13:23:55 +01:00
MartinBraquet
b7fe357fb2 Translate filter for last online 2026-02-27 13:22:15 +01:00
MartinBraquet
59d52d4c11 Add filter for last online 2026-02-27 13:01:42 +01:00
MartinBraquet
8c1a75e26b Reduce profile card modal height 2026-02-27 11:33:52 +01:00
MartinBraquet
ce8e7d141a Translate view profile card 2026-02-27 00:17:35 +01:00
MartinBraquet
0a2e4a7df1 Add profile card to profile 2026-02-26 23:56:15 +01:00
MartinBraquet
26bc68e4db Fix 2026-02-26 19:25:49 +01:00
MartinBraquet
945f4a0d82 Fix [Signup Flow] Unable to edit User/Display name during flow after clicking next #32 2026-02-26 19:21:21 +01:00
MartinBraquet
41da848714 Show share button on mobile as well 2026-02-26 17:21:12 +01:00
MartinBraquet
5a92c47c99 Add docs 2026-02-26 17:15:35 +01:00
MartinBraquet
69f181e8ee Fix package name 2026-02-26 16:57:16 +01:00
MartinBraquet
f374fef4f9 Double check API deploy workflow 2026-02-26 16:54:17 +01:00
MartinBraquet
263e38f23e Separate version check from action 2026-02-26 16:52:24 +01:00
MartinBraquet
ddd5cd6823 Fix missing keystore 2026-02-26 16:47:03 +01:00
MartinBraquet
7f8f394d58 Rename releases CD 2026-02-26 16:38:11 +01:00
MartinBraquet
57d9d2df38 Fix java version 2026-02-26 16:33:17 +01:00
MartinBraquet
b7500ba634 Ignore *my-release-key.keystore 2026-02-26 16:22:30 +01:00
MartinBraquet
615d56131f Fix missing google services 2026-02-26 16:22:12 +01:00
MartinBraquet
c6f4b05e2a Push asset for android release automation 2026-02-26 16:06:34 +01:00
MartinBraquet
366581bcb1 Test 2026-02-26 15:42:38 +01:00
MartinBraquet
35d96fff5d Fix path 2026-02-26 15:42:06 +01:00
MartinBraquet
24e088b599 Add workflow for android release to testing track 2026-02-26 15:28:23 +01:00
MartinBraquet
432d2df449 Set npx capacitor-assets generate --android 2026-02-26 14:47:22 +01:00
MartinBraquet
68a79c4b90 Clean 2026-02-26 14:46:56 +01:00
MartinBraquet
fa922bdcbe Fix 2026-02-26 11:23:01 +01:00
MartinBraquet
1086f6b4e2 Fix share social 2026-02-26 11:15:50 +01:00
MartinBraquet
44d3e7577b Improve OG layout for scare profile 2026-02-26 11:10:07 +01:00
MartinBraquet
4015db7fda Fix share button 2026-02-26 10:03:59 +01:00
MartinBraquet
04f41c42c4 Clean button 2026-02-26 01:55:54 +01:00
MartinBraquet
67fb98c672 Show OG card at the end of onboarding 2026-02-26 01:41:31 +01:00
MartinBraquet
8c21d2990f Set imageUrl for FCM 2026-02-25 23:21:01 +01:00
MartinBraquet
32201b6dfa Improve OG 2026-02-25 23:20:45 +01:00
MartinBraquet
4326c870a8 Fix icon bubble message 2026-02-25 23:20:28 +01:00
MartinBraquet
e03c714555 Release 2026-02-25 21:35:21 +01:00
MartinBraquet
59cb649540 Fix keyword search 2026-02-25 21:34:22 +01:00
MartinBraquet
77e40c088c Fix 2026-02-25 21:05:13 +01:00
MartinBraquet
e5aeda92c8 Clean capitalize 2026-02-25 20:48:37 +01:00
MartinBraquet
0e99f75b73 Release 2026-02-25 20:00:07 +01:00
MartinBraquet
9ec5fe549b Add support for OG card 2026-02-25 19:55:28 +01:00
MartinBraquet
47cf7bd3b2 Force white background in OG 2026-02-25 15:48:46 +01:00
MartinBraquet
c848007874 Add headline to SEO description 2026-02-25 15:35:18 +01:00
MartinBraquet
abba1260be Add headline 2026-02-25 15:34:02 +01:00
MartinBraquet
cfc6b45a5b Improve SEO image 2026-02-25 15:23:56 +01:00
MartinBraquet
5e8f8167d1 Clean color 2026-02-25 14:40:05 +01:00
MartinBraquet
dce0821b1a Fixes want kids lavel in filters format 2026-02-25 14:31:20 +01:00
MartinBraquet
129dde8713 Release 2026-02-24 23:29:04 +01:00
MartinBraquet
5d368a61eb Fixes interest indicator section 2026-02-24 23:25:57 +01:00
MartinBraquet
2d0a869b00 Improve french wording 2026-02-24 23:25:28 +01:00
MartinBraquet
88efbe4666 Increase subtitle font size 2026-02-24 23:25:04 +01:00
MartinBraquet
46aba5dc8d Fix test for create channel 2026-02-24 19:59:32 +01:00
MartinBraquet
5321dd5690 Entre vous 2026-02-24 19:59:18 +01:00
MartinBraquet
2b0cd7ad3a aAdd notif for interest indicator 2026-02-24 19:59:02 +01:00
MartinBraquet
3c08ba3cae Clean 2026-02-24 19:21:20 +01:00
MartinBraquet
f850b4ada5 API release 2026-02-24 19:09:34 +01:00
MartinBraquet
1dbe4ecdef Improve colors buttons connect-actions.tsx 2026-02-24 19:09:17 +01:00
MartinBraquet
2b31ed3164 Add notifications for interest indicator 2026-02-24 18:00:12 +01:00
MartinBraquet
df2473929a Add connection interest notification and update related API logic 2026-02-24 17:32:36 +01:00
MartinBraquet
80a877301a Add connection preferences feature with API endpoints and UI updates 2026-02-24 17:07:59 +01:00
MartinBraquet
1aae688f3f Update CI configuration for E2E tests and add Supabase start step 2026-02-24 00:58:12 +01:00
MartinBraquet
337ce4523f Replace triangle icons with chevron icons in profile comments 2026-02-24 00:47:35 +01:00
MartinBraquet
e0b26af2bc Update padding in show-more component for better spacing 2026-02-24 00:38:05 +01:00
MartinBraquet
1e2c2bbb8f Update padding in show-more component for better spacing 2026-02-24 00:36:21 +01:00
MartinBraquet
ab0fd0aea4 Add localization support for relative time formatting 2026-02-24 00:36:09 +01:00
MartinBraquet
1310c423bd Update editable bio styles for improved readability 2026-02-24 00:35:42 +01:00
MartinBraquet
32fadcc194 Disable immediate rendering in editor configuration 2026-02-24 00:35:12 +01:00
MartinBraquet
a2959a773e Refactor profile forms to simplify bio handling and remove conditional rendering 2026-02-24 00:09:27 +01:00
MartinBraquet
cadb4a4fd5 Bump version to 1.15.0 in package.json 2026-02-24 00:02:59 +01:00
MartinBraquet
8decdab0c3 Refactor notification tests to improve assertions and add locale support 2026-02-24 00:01:39 +01:00
MartinBraquet
b710fa9f60 Finish notif translations 2026-02-23 23:49:51 +01:00
MartinBraquet
3cb5d08801 Update remotePatterns to include martinbraquet.com 2026-02-23 23:46:54 +01:00
MartinBraquet
aa785c1539 Lint fix email package 2026-02-23 22:20:31 +01:00
MartinBraquet
f0c645b16d Add linting and typechecking scripts for backend/email 2026-02-23 22:19:01 +01:00
MartinBraquet
9870ac5029 Add translations for notifications 2026-02-23 18:39:25 +01:00
MartinBraquet
cd067cd1a9 Replace TriangleDownFillIcon with ChevronDown in ReplyToggle component 2026-02-23 15:09:52 +01:00
MartinBraquet
e1805d9d9e Add ignore-engines setting to .yarnrc and update package.json to include pg-query-stream dependency 2026-02-23 15:05:48 +01:00
MartinBraquet
23a8aa6712 Refactor avatar URL assignment and simplify user retrieval in create-user logic 2026-02-23 14:58:45 +01:00
MartinBraquet
f70a74d20e Upgrade eslint to v9 2026-02-23 14:48:03 +01:00
MartinBraquet
02ea9131e4 Bump version to 1.13.2 in package.json 2026-02-23 14:14:44 +01:00
MartinBraquet
cd8096f524 Update dist:copy script to include messages directory in copy process 2026-02-23 14:09:15 +01:00
MartinBraquet
52819f3259 Upgrade 2026-02-23 13:48:42 +01:00
MartinBraquet
55c1b3983d Refactor message loading and fix paths for translation files 2026-02-23 13:48:23 +01:00
MartinBraquet
27ac1539cb Add locale retrieval from cookies in auth context and improve logging 2026-02-23 13:22:34 +01:00
MartinBraquet
119bd9699d Refactor createUser function to simplify device token handling and improve logging 2026-02-23 13:18:26 +01:00
MartinBraquet
607285f25d Update @tiptap dependencies to version 2.10.4 in package.json 2026-02-23 13:13:41 +01:00
MartinBraquet
192a944f4b Add multi language support for emails 2026-02-23 12:54:43 +01:00
MartinBraquet
c085e8f6dd Add guidelines for adding translations to existing files 2026-02-22 21:51:08 +01:00
MartinBraquet
79f855d39a Add EnglishOnlyWarning component and display warning in relevant pages 2026-02-22 21:50:59 +01:00
MartinBraquet
f3cb8d51fc Bump version to 1.10.0 in package.json 2026-02-22 21:07:58 +01:00
MartinBraquet
1bb9ac2006 Update French translation for account deletion confirmation label 2026-02-22 18:34:37 +01:00
MartinBraquet
07c8f74de7 Refactor filter initialization logic in useFilters for improved readability 2026-02-22 18:34:21 +01:00
MartinBraquet
e305fba93d Add reason tracking for user deletion in unit tests 2026-02-22 18:20:15 +01:00
MartinBraquet
ce680d6c8a Prompt for reasons upon profile deletion 2026-02-22 18:06:38 +01:00
MartinBraquet
5362403a04 Add language mapping based on locale for signup and filters 2026-02-22 16:46:30 +01:00
MartinBraquet
abd77c4c4b Add support for language filter based on signup locale in useFilters 2026-02-22 16:33:00 +01:00
MartinBraquet
e4a9337fab Fix 2026-02-22 16:27:32 +01:00
MartinBraquet
a9d9f0a190 Improve debug logging in run-script.ts for clarity 2026-02-22 10:42:09 +01:00
MartinBraquet
a112350ad4 Bump version to 1.11.1 2026-02-21 19:49:46 +01:00
MartinBraquet
243e602fda Refactor pre-commit hooks and update lint-staged configuration 2026-02-21 19:48:17 +01:00
MartinBraquet
b6d7955130 Add profile field: place they grew up 2026-02-21 19:47:26 +01:00
MartinBraquet
0277cc390d Update French translations for political affiliations 2026-02-21 18:08:50 +01:00
MartinBraquet
2d85b5c25a Bump version to 1.8.0 and increment version code to 39 2026-02-21 11:45:20 +01:00
MartinBraquet
837a45dd91 Add optional environment variable logging to runScript 2026-02-21 11:45:03 +01:00
MartinBraquet
b941960013 Enhance event card with timezone display and improve date formatting 2026-02-20 22:57:59 +01:00
MartinBraquet
650b3f2469 Clean 2026-02-20 19:29:54 +01:00
MartinBraquet
3a1273dfac Fix 2026-02-20 19:00:50 +01:00
MartinBraquet
b7ab669adc Test API release 2026-02-20 18:55:09 +01:00
MartinBraquet
affea2ce26 Fix 2026-02-20 18:53:43 +01:00
MartinBraquet
85ea90b9c8 Rollback failing API deployment 2026-02-20 18:49:22 +01:00
MartinBraquet
0bfc714a41 Clean 2026-02-20 17:41:53 +01:00
Martin Braquet
ba9b3cfb06 Add pretty formatting (#29)
* Test

* Add pretty formatting

* Fix Tests

* Fix Tests

* Fix Tests

* Fix

* Add pretty formatting fix

* Fix

* Test

* Fix tests

* Clean typeckech

* Add prettier check

* Fix api tsconfig

* Fix api tsconfig

* Fix tsconfig

* Fix

* Fix

* Prettier
2026-02-20 17:32:27 +01:00
Okechi Jones-Williams
1994697fa1 Adding onboarding E2E foundations and first tests (#30)
* .

* Centralizing config details

* Added data-testId attributes where necessary and started the onboarding flow scaffolding

* Continued onboarding test scaffolding

* Continued work on tests for the Onboarding flow

* .

* Updated "Want kids" options to be less flaky
Updated playwright.config so that expect timeout matching test timeout

* Continued updating front-end scaffolding

* .

* .

* .

* .

* Updated fixture function deleteUser: to also remove the database user information

* Rm

* Fix

* Fixes

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2026-02-20 16:56:26 +01:00
MartinBraquet
1c26b6381e Remove test files 2026-02-19 17:24:29 +01:00
MartinBraquet
f5daa3cdc8 Test pre-commit formatting with updated config 2026-02-19 17:24:05 +01:00
MartinBraquet
8c71f6231a Test pre-commit formatting 2026-02-19 17:23:19 +01:00
MartinBraquet
2f8e6147f6 Configure pre-commit hook to format files with Prettier 2026-02-19 17:22:42 +01:00
MartinBraquet
2ff778a387 Add husky 2026-02-19 17:19:03 +01:00
MartinBraquet
70c3493057 Fix 2026-02-19 17:15:50 +01:00
MartinBraquet
e62dac276a Fix 2026-02-19 17:05:41 +01:00
MartinBraquet
1115b4f1b0 Test 2026-02-19 17:03:47 +01:00
MartinBraquet
c05af9ffcb Fix 2026-02-19 17:02:13 +01:00
MartinBraquet
2bb84d3992 Fix 2026-02-19 17:01:19 +01:00
MartinBraquet
2c90fc6cc8 Fix linting 2026-02-19 16:34:39 +01:00
MartinBraquet
d89bf2d92a Test 2026-02-19 15:51:24 +01:00
MartinBraquet
f591cb13bb Upgrade packages and pre commit lint 2026-02-19 15:43:32 +01:00
MartinBraquet
52720d2520 Upgrade packages, add lint and type check 2026-02-19 15:08:38 +01:00
MartinBraquet
45473e0679 API Release 2026-02-19 14:41:35 +01:00
MartinBraquet
5e3c10d26a Fix /get-channel-memberships and /get-channel-seen-time called all the time 2026-02-19 14:41:11 +01:00
MartinBraquet
d0c58f753b Clean 2026-02-19 14:32:10 +01:00
MartinBraquet
20ba6f7ea9 Adjust padding in compatibility question content layout 2026-02-19 14:32:00 +01:00
MartinBraquet
27e93da06a Make answers to compatibility prompts searchable 2026-02-19 14:11:22 +01:00
MartinBraquet
c1a204e3be Update import path for user information choices 2026-02-19 12:34:12 +01:00
MartinBraquet
6be69b74e8 Fix user notif test 2026-02-19 12:23:15 +01:00
MartinBraquet
4b894363af Translate saved searches 2026-02-19 12:19:35 +01:00
MartinBraquet
cf843f66c4 Refactor measurement system imports and enhance filter formatting 2026-02-19 01:30:59 +01:00
MartinBraquet
cb98314bec Add metric vs imperial switch 2026-02-19 01:00:58 +01:00
MartinBraquet
4046cc19ef Add referral invitation titles in German and French translations 2026-02-18 22:58:20 +01:00
MartinBraquet
6787cdffa3 Remove impossible options for min max age 2026-02-18 22:53:50 +01:00
MartinBraquet
d583dbb945 Implement notification template system and bulk notification creation 2026-02-18 22:42:02 +01:00
MartinBraquet
bbd395b904 Add confirmation prompt for event cancellation and update translations 2026-02-18 21:54:53 +01:00
MartinBraquet
e1077e95a2 Fix 2026-02-18 21:37:59 +01:00
MartinBraquet
28f33da6d0 Enhance event creation with locale-aware date formatting and improved translations 2026-02-18 21:02:26 +01:00
MartinBraquet
dbdaa9d7ec Remove unused useIsPageVisible hook and adjust event fetching to only occur on initial mount 2026-02-18 20:14:04 +01:00
MartinBraquet
f4f009dc4a Refactor UserLinkFromId component to enforce userId type and remove null checks 2026-02-18 20:08:32 +01:00
MartinBraquet
aacdd380c4 Update French translation for event cancellation message 2026-02-18 20:08:27 +01:00
MartinBraquet
01f774ef7e Add UserLinkFromId component for improved user linking 2026-02-18 20:01:20 +01:00
MartinBraquet
57d7df63c8 Fix margin 2026-02-18 19:35:26 +01:00
MartinBraquet
f41c05a338 Fix translation 2026-02-18 19:33:01 +01:00
MartinBraquet
66526abeef Improve migration cleanup by using find command 2026-02-18 19:04:50 +01:00
MartinBraquet
3772d28129 Fix onboarding with no questions 2026-02-18 19:02:50 +01:00
MartinBraquet
3cc7222309 Fix 2026-02-18 18:36:09 +01:00
MartinBraquet
46082c5f64 Add events page 2026-02-18 18:34:18 +01:00
MartinBraquet
cad1fd72e3 Fix firebase emulator for storage 2026-02-18 17:46:06 +01:00
MartinBraquet
3a2e932628 Use md prose 2026-02-18 17:45:38 +01:00
MartinBraquet
1b3b40917b Seed dev_1 profile in db 2026-02-18 13:59:06 +01:00
MartinBraquet
c181c571c4 Update migration script to clear existing files and adjust timestamp 2026-02-18 13:58:36 +01:00
MartinBraquet
8b094769e4 Apply sqlmatch 2026-02-18 13:43:25 +01:00
MartinBraquet
4b2b46d4f7 Add link to development documentation for project setup guidelines 2026-02-17 22:51:01 +01:00
MartinBraquet
fc6628be08 Refactor unit tests to use sqlMatch for SQL query assertions 2026-02-17 22:37:32 +01:00
MartinBraquet
07ce2780c6 Refactor user profile handling by removing deprecated fields and improving link management 2026-02-17 22:02:47 +01:00
MartinBraquet
c8b7b85391 Add color-coded status and error messages to run_local_isolated.sh 2026-02-17 20:08:27 +01:00
MartinBraquet
0af6b9aff5 Allow profile creation step to display when profile is already created 2026-02-17 20:05:57 +01:00
MartinBraquet
494e62720d Add loading indicator for profile page while fetching user data 2026-02-17 19:55:08 +01:00
MartinBraquet
8df2be1969 Fix 2026-02-17 19:46:27 +01:00
MartinBraquet
3c59be763a Add translation guidelines and coding tips to documentation 2026-02-17 19:41:03 +01:00
MartinBraquet
60a44b2ed1 Add age validation and error messages to optional profile form 2026-02-17 19:36:01 +01:00
MartinBraquet
e58a3ecb43 Fix age selection logic to ensure min is not greater than max 2026-02-17 19:17:44 +01:00
MartinBraquet
20025c825f Fix 2026-02-17 17:41:42 +01:00
MartinBraquet
aec30cd5b5 Fix 2026-02-17 17:40:20 +01:00
MartinBraquet
771cf887d5 Fix 2026-02-17 17:33:31 +01:00
MartinBraquet
78325cddfa Fix 2026-02-17 17:27:45 +01:00
MartinBraquet
1428ef1687 Fix 2026-02-17 16:39:32 +01:00
MartinBraquet
145f544ff1 Fix 2026-02-17 16:39:23 +01:00
MartinBraquet
28a582d9c4 Install from lock file 2026-02-17 16:17:42 +01:00
MartinBraquet
19fc2b798b Add env test 2026-02-17 16:17:34 +01:00
MartinBraquet
922decd252 Clean 2026-02-17 16:08:23 +01:00
MartinBraquet
0188a4ab51 Prepend npx 2026-02-17 16:07:07 +01:00
MartinBraquet
c47e693e69 Clean 2026-02-17 15:54:58 +01:00
MartinBraquet
dd049dfb88 Fix 2026-02-17 15:15:21 +01:00
MartinBraquet
e6d64f2668 Add test docs 2026-02-17 15:13:27 +01:00
MartinBraquet
ee1e894e2f Add e2e dev 2026-02-17 15:13:16 +01:00
MartinBraquet
b3365cd773 Clean 2026-02-17 15:12:58 +01:00
MartinBraquet
920e0f37f2 Upgrade java 2026-02-17 15:12:47 +01:00
MartinBraquet
fd7b4edc02 Clean 2026-02-17 14:33:52 +01:00
MartinBraquet
e689d0253c Fix lint 2026-02-17 13:45:19 +01:00
MartinBraquet
9efd3e4510 Fix 2026-02-17 13:42:43 +01:00
MartinBraquet
4fda21c582 Fix 2026-02-17 13:40:53 +01:00
MartinBraquet
76abe4ad28 Fix 2026-02-17 13:23:21 +01:00
MartinBraquet
e92f8afb46 Fix deps 2026-02-17 13:17:36 +01:00
MartinBraquet
e3907a3e64 Fix 2026-02-17 13:16:36 +01:00
MartinBraquet
5c9aa4f9f0 Fix 2026-02-17 13:13:33 +01:00
MartinBraquet
ae3b045772 Fix 2026-02-17 13:08:21 +01:00
MartinBraquet
6638d2b184 Fix 2026-02-17 13:02:47 +01:00
MartinBraquet
6a97045bad Fix lint 2026-02-17 12:47:20 +01:00
MartinBraquet
ba5beea17e Add ai assistant guidelines copilot 2026-02-17 12:41:50 +01:00
MartinBraquet
7df3b301c6 Add ai assistant guidelines 2026-02-17 12:34:35 +01:00
MartinBraquet
ab81949927 Fix java version 2026-02-17 12:34:22 +01:00
MartinBraquet
f7c0d77e9c Add local supabase for DB isolation 2026-02-17 12:10:17 +01:00
MartinBraquet
b7d1fd9903 Clean button UI 2026-02-16 13:38:33 +01:00
Martin Braquet
e3b743f87b Add font preference selector in settings (#28)
* Add font preference selector in settings

* Consolidate font family config into shared global constant

* Revert "Consolidate font family config into shared global constant"

This reverts commit 789ddc98e1.

* Fix
2026-02-16 12:24:45 +01:00
MartinBraquet
54e1106237 Fix 2026-02-15 17:15:15 +01:00
MartinBraquet
6a18d482e2 API release 2026-02-15 17:01:23 +01:00
MartinBraquet
c46fc2a5bd Prevent usernmaes ilike existing ones (case-insensitive) 2026-02-15 16:57:46 +01:00
MartinBraquet
e28263ff1f Fix comments 2026-02-15 13:45:34 +01:00
MartinBraquet
117b2d22e4 Fix 2026-02-15 13:41:42 +01:00
MartinBraquet
d61133ef74 Cache data for required form as well 2026-02-14 01:50:46 +01:00
MartinBraquet
ccf68b80c0 Fix 2026-02-14 01:09:04 +01:00
MartinBraquet
d2929a94ce Cache unsaved profile editions for 24h 2026-02-14 01:00:20 +01:00
MartinBraquet
7fd509b7e4 Add back button to md pages 2026-02-14 00:58:35 +01:00
MartinBraquet
2c3314becc Move FirebaseUser to context to avoid duplicated subscriptions 2026-02-13 22:55:02 +01:00
MartinBraquet
6815513ac3 Fix download file for android 2026-02-13 22:18:25 +01:00
MartinBraquet
e8cfc77902 Fix email verif not refreshing component 2026-02-13 21:03:49 +01:00
MartinBraquet
7928e58d3b Update todo 2026-02-13 18:32:43 +01:00
MartinBraquet
d8743a4b1c API release 2026-02-13 18:17:39 +01:00
MartinBraquet
6626f23ef3 Android release 2026-02-13 18:07:17 +01:00
MartinBraquet
a0c1cf964b Allow download on native mobile 2026-02-13 17:59:13 +01:00
MartinBraquet
7437a2fb45 Fix DM not parsed in data export 2026-02-13 17:36:12 +01:00
MartinBraquet
8ff5b8a577 Allow users to download all their data 2026-02-13 16:50:06 +01:00
MartinBraquet
cd434e2fb5 Move pics above compat prompts 2026-02-13 15:46:26 +01:00
MartinBraquet
7734b689a3 Add Big 5 profile field 2026-02-13 15:23:54 +01:00
MartinBraquet
ca55a93d5f Add indices 2026-02-13 14:15:14 +01:00
MartinBraquet
6b7e3acf93 Fix double success 2026-02-13 13:17:52 +01:00
MartinBraquet
e865f75d95 Android release 2026-02-13 13:11:07 +01:00
MartinBraquet
af95174929 Refresh page after email verified 2026-02-13 13:11:02 +01:00
MartinBraquet
b6598f917c Fix flash 2026-02-13 00:53:52 +01:00
MartinBraquet
702d4ea1a2 Fix button UI, default gray 2026-02-13 00:44:13 +01:00
MartinBraquet
dbe45d5181 Fix flash 2026-02-13 00:36:11 +01:00
MartinBraquet
4e11cc7ed1 API release 2026-02-13 00:31:50 +01:00
MartinBraquet
2d5184a0ee Fix: update avatar pic to be the same as pinned profile pic 2026-02-13 00:28:06 +01:00
MartinBraquet
1d2a2beb7a Fix 2026-02-12 23:46:11 +01:00
MartinBraquet
60dba612ba Fix tests 2026-02-12 23:37:42 +01:00
MartinBraquet
ea4047bc47 Fix 2026-02-12 23:03:23 +01:00
MartinBraquet
fab2316f28 Warn user in toast if need to wait before sending verif email again 2026-02-12 22:57:37 +01:00
MartinBraquet
ae708313aa Log 2026-02-12 17:27:25 +01:00
MartinBraquet
812f6acac7 Release API 2026-02-12 17:14:55 +01:00
MartinBraquet
f56373fd73 People must verify their email to send messages 2026-02-12 17:14:40 +01:00
MartinBraquet
5d9a1c1bf8 Allow bio edition in profile edition 2026-02-12 15:49:04 +01:00
MartinBraquet
a3257cd4c0 Add insta 2026-02-12 13:56:12 +01:00
MartinBraquet
67f8861d12 Fix alignment 2026-02-12 13:43:24 +01:00
MartinBraquet
3fd37b21f3 Clean UI 2026-02-12 13:34:12 +01:00
MartinBraquet
9c1fbbd258 Fix typo 2026-02-12 13:05:37 +01:00
MartinBraquet
32fb79e9a2 Clean 2026-02-12 12:50:39 +01:00
MartinBraquet
ebecf18b6c Improve button UI 2026-02-12 12:23:03 +01:00
MartinBraquet
403af8106f Swap eye symbol: show eye off when hidden profile 2026-02-12 12:14:46 +01:00
MartinBraquet
85ceee0b0d Fix unit test 2026-02-11 22:36:26 +01:00
MartinBraquet
33f75420a2 Android release 2026-02-11 19:22:18 +01:00
MartinBraquet
a20767761b Fix hidden profiles not updating 2026-02-11 19:11:33 +01:00
MartinBraquet
b13a40f892 Use persistent in storage for hidden profiles and allow toggle hidden profile 2026-02-11 18:25:58 +01:00
MartinBraquet
a86f841b7e Remove toasts profile grid 2026-02-11 17:35:58 +01:00
MartinBraquet
41c9da04b1 Remove toasts 2026-02-11 17:32:22 +01:00
MartinBraquet
f42a1ad64f Fix tooltip (3) 2026-02-11 16:52:37 +01:00
MartinBraquet
9c00fffb89 Throttle toasts 2026-02-11 16:52:27 +01:00
MartinBraquet
7b07610613 Fix 2026-02-11 16:42:23 +01:00
MartinBraquet
f1bc2f9dcb Use loading indicator 2026-02-11 16:37:23 +01:00
MartinBraquet
4a317018cb Fix tooltip message still showing after click on mobile on profile card (2) 2026-02-11 16:37:15 +01:00
MartinBraquet
05219b2938 Fix tooltip message still showing after click on mobile on profile card 2026-02-11 16:25:58 +01:00
MartinBraquet
32edb4697c Release API 2026-02-11 16:13:37 +01:00
MartinBraquet
7e790ca353 Add hidden_profiles table 2026-02-11 16:13:26 +01:00
MartinBraquet
939767cb42 Improve layout for saved people 2026-02-11 16:12:46 +01:00
MartinBraquet
31dc39fad7 Show the profiles I hid in the settings 2026-02-11 15:58:18 +01:00
MartinBraquet
243d22822a Add hide feature to profile page 2026-02-11 15:13:42 +01:00
MartinBraquet
17d0fba831 Hide feature to hide per-user profiles 2026-02-11 14:57:27 +01:00
MartinBraquet
05d003535b Sort lines 2026-02-11 14:13:36 +01:00
MartinBraquet
bd39bc290c Fix wrap 2026-02-11 13:59:47 +01:00
MartinBraquet
4b203ae686 Android release 2026-02-11 13:50:43 +01:00
MartinBraquet
4e306af344 Fix 2026-02-11 13:50:38 +01:00
MartinBraquet
46489e25ff Fix translation 2026-02-11 13:36:44 +01:00
MartinBraquet
56d76922dc Fix logo not working when loading /messages/[id] 2026-02-11 13:28:58 +01:00
MartinBraquet
6ad3d3051f Show messages of deleted users 2026-02-11 13:16:28 +01:00
MartinBraquet
7598f32c56 Update financials 2026-02-11 12:01:00 +01:00
MartinBraquet
46fcf042ad Improve /socials page 2026-02-10 13:19:40 +01:00
MartinBraquet
5962512a83 Improve /organisation page 2026-02-10 13:03:14 +01:00
MartinBraquet
eaaae99dff Improve tracking: add user ID, etc. 2026-02-09 17:19:21 +01:00
MartinBraquet
688bbffd5e Add RCF interview to press 2026-02-09 16:38:21 +01:00
MartinBraquet
079ec8fc7e Improve filters UI 2026-02-09 14:41:14 +01:00
MartinBraquet
fba4436a08 Add tooltip to CompatibleBadge and fix z-index in profile-grid 2026-02-09 13:09:54 +01:00
MartinBraquet
d7f7e046ed Update TESTING.md with Jest clarification and Playwright selector guide 2026-02-07 14:16:30 +01:00
MartinBraquet
e001787e5c Add run_local_emulated.sh 2026-02-06 13:05:48 +01:00
MartinBraquet
cc4277a85c Add storage to firebase emulation 2026-02-06 13:05:31 +01:00
MartinBraquet
b49838c49a Fix 2026-02-01 17:23:04 +01:00
MartinBraquet
33ce2c9624 Fix 2026-02-01 17:22:29 +01:00
MartinBraquet
52d09aedd2 Fix 2026-02-01 16:40:26 +01:00
MartinBraquet
cf67ad0d81 Fix 2026-02-01 16:39:31 +01:00
MartinBraquet
20006049d0 Fix 2026-02-01 16:31:27 +01:00
MartinBraquet
a0ce449abe Android release 2026-02-01 16:09:08 +01:00
MartinBraquet
bd8b371c13 Fix faq 2026-01-31 20:12:32 +01:00
MartinBraquet
83433be1c8 Comment line 2026-01-31 15:37:22 +01:00
MartinBraquet
7596906531 Fix scrolling warning 2026-01-31 15:19:56 +01:00
MartinBraquet
cb35813d16 Fix margin 2026-01-31 15:11:33 +01:00
MartinBraquet
55027c790a Fix fiilter coomp 2026-01-31 14:59:43 +01:00
MartinBraquet
09ff56ad61 Fix redirect 2026-01-31 14:59:36 +01:00
MartinBraquet
63e516fb4b Fix text 2026-01-31 14:48:26 +01:00
MartinBraquet
0b2b60bd49 Add onboarding for profiles page 2026-01-31 14:43:22 +01:00
MartinBraquet
3e8470a216 Improve site log margin 2026-01-31 12:48:18 +01:00
MartinBraquet
e04746cde3 Fix sidebar on mobile (was scrolling) 2026-01-31 12:30:02 +01:00
MartinBraquet
8c293c69eb Update with bio to
completed
2026-01-31 11:48:50 +01:00
MartinBraquet
cb96528752 Move next button toward center on desktop 2026-01-30 23:58:37 +01:00
MartinBraquet
8348953510 Hide nav bar while signing up 2026-01-30 23:58:19 +01:00
MartinBraquet
6d25937b56 Apply modal height to all modals 2026-01-30 23:40:45 +01:00
MartinBraquet
d1f1fe945f Improve onboarding margins 2026-01-30 23:03:12 +01:00
MartinBraquet
4155beb7db Fix bottom padding mobile for compat Q 2026-01-30 23:02:55 +01:00
MartinBraquet
374143172d Fix dvh and add hloss 2026-01-30 22:30:42 +01:00
MartinBraquet
6b4b0a9459 Fix dvh 2026-01-30 22:23:52 +01:00
MartinBraquet
21789101fd Fix scroll 2026-01-30 18:50:07 +01:00
MartinBraquet
8bfed9a6cc Fix 2026-01-30 18:42:13 +01:00
MartinBraquet
c36ceb7ed9 Add onboarding 2026-01-30 18:34:02 +01:00
MartinBraquet
e80d8d701a Fix modal layout, make it larger on mobile 2026-01-30 18:33:27 +01:00
MartinBraquet
f336e61304 Active instead of last connected 2026-01-30 14:29:19 +01:00
MartinBraquet
70194541db Show all work in card and fix comma 2026-01-30 14:23:32 +01:00
MartinBraquet
8570f74a24 Trim trailing whitespaces in profile data 2026-01-30 14:23:18 +01:00
MartinBraquet
5b43382fc7 Fix profile list not loading more 2026-01-28 23:26:59 +01:00
MartinBraquet
237ed0b5bf Release 2026-01-28 22:59:09 +01:00
MartinBraquet
b01dcc6bde Include short bios by default and fill those no-bio cards with work and interests 2026-01-28 22:58:44 +01:00
MartinBraquet
a433d1e095 Fix 2026-01-28 14:25:14 +01:00
MartinBraquet
ccc68f00ae Fix bg 2026-01-28 14:20:05 +01:00
MartinBraquet
756f0036eb Fixes 2026-01-28 14:14:14 +01:00
MartinBraquet
54b8ab34bd Fix 2026-01-28 14:02:41 +01:00
MartinBraquet
59ebd539f1 Add QuestionMarkTooltip 2026-01-28 13:50:45 +01:00
MartinBraquet
7fad10d203 Revert "Fix"
This reverts commit acb29c0600.
2026-01-28 13:24:12 +01:00
MartinBraquet
acb29c0600 Fix 2026-01-28 13:19:14 +01:00
MartinBraquet
398c7b92b7 Fix couriel 2026-01-28 13:07:48 +01:00
MartinBraquet
95a9d8a50a translate copy link buttons 2026-01-28 13:00:35 +01:00
MartinBraquet
95c018786d Inform user about filtered profiles 2026-01-28 12:51:17 +01:00
MartinBraquet
d7053dee14 Translate MoreOptionsUserButton 2026-01-28 12:27:14 +01:00
MartinBraquet
f878521a40 Translate star button and editor buttons 2026-01-28 12:20:58 +01:00
MartinBraquet
40bbdb3fd9 Translate message button 2026-01-28 12:13:46 +01:00
MartinBraquet
7ac933160d Fix interest (etc.) shown as IDs instead of labels in saved searches and emails 2026-01-27 23:04:06 +01:00
MartinBraquet
0ac5ce7b41 Android release 2026-01-26 23:41:26 +01:00
MartinBraquet
152ed99727 Fix test 2 2026-01-26 23:32:21 +01:00
MartinBraquet
a48633a074 Fix test 2026-01-26 23:17:52 +01:00
MartinBraquet
b2ebf7e78e Release API 2026-01-26 23:14:21 +01:00
MartinBraquet
a5259f4c61 Fix minor bugs 2026-01-26 23:13:32 +01:00
MartinBraquet
542152eadb Store option IDs instead of EN labels in profiles and make keyword search match selected options 2026-01-26 22:53:31 +01:00
MartinBraquet
ffc966f3b3 Render keyword examples in the user language 2026-01-26 22:48:28 +01:00
MartinBraquet
e45b24880f Clarify bio role 2026-01-26 14:18:22 +01:00
MartinBraquet
beac17cecf Add file format warning 2026-01-26 00:00:37 +01:00
MartinBraquet
7ca682a4f9 Fix storage name 2026-01-25 23:48:17 +01:00
MartinBraquet
07bc32d781 Fix HEIC photos not rendered 2026-01-25 23:40:18 +01:00
MartinBraquet
d59961f6cc Fix test 2026-01-25 22:47:53 +01:00
MartinBraquet
945febffb6 Android release 2026-01-25 22:47:34 +01:00
MartinBraquet
bda34dddd0 Add translation support for compatibility prompts 2026-01-25 22:18:54 +01:00
MartinBraquet
ccb2eaaddf Add press article summaries 2026-01-22 21:27:34 +01:00
MartinBraquet
7880c391f1 Add l'avenir press article 2026-01-22 17:45:54 +01:00
MartinBraquet
ad4ea7328f Fix AI assistant that removed 2 translations... 2026-01-22 17:30:33 +01:00
MartinBraquet
763b74ef31 Show no kids in profile about 2026-01-22 14:04:07 +01:00
MartinBraquet
851d945545 Add multi-lingual support for fromNow() 2026-01-21 13:42:37 +01:00
MartinBraquet
80af2e9aeb Add categories to profile form 2026-01-21 12:55:09 +01:00
MartinBraquet
473734bbd4 Fixes 2026-01-21 11:28:03 +01:00
MartinBraquet
afe36f98be Fix 2026-01-21 11:23:34 +01:00
MartinBraquet
0888eb1a81 Improve test docs 2026-01-21 11:21:30 +01:00
MartinBraquet
e2cbb5969d Use yarn 2026-01-21 11:20:01 +01:00
Okechi Jones-Williams
026a938e6f Added/Updated Documentation (#27)
* Updating documentation

* Added folder structure

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2026-01-21 09:07:10 +01:00
Martin Braquet
2da7e6d5d9 Enhance language addition guidelines in development.md
Updated instructions for adding a new language with LLM translation tips.
2026-01-20 21:24:13 +01:00
MartinBraquet
fd331dfaf9 Do not render relationship_status in match emails 2026-01-20 21:12:42 +01:00
MartinBraquet
e4a3e7a525 Move work area below job title 2026-01-20 21:11:55 +01:00
MartinBraquet
71695aba6c Improve button for reset filters 2026-01-20 13:35:18 +01:00
MartinBraquet
1aff8c1009 Fix missing space in terms 2026-01-19 20:41:22 +01:00
MartinBraquet
61a2e31175 Fix: Remove status bar black background in light theme 2026-01-19 20:14:35 +01:00
MartinBraquet
c3d547f090 Fix FR typo 2026-01-19 20:02:17 +01:00
MartinBraquet
33d6dc1455 Clarify kids name 2026-01-19 14:32:40 +01:00
MartinBraquet
43c3ef591c Android release 2026-01-19 14:14:16 +01:00
MartinBraquet
79d56b0b4b Clean 2026-01-19 14:06:05 +01:00
MartinBraquet
1d505a2ae3 Fix status bar not visible in light theme 2026-01-19 14:06:01 +01:00
MartinBraquet
218de89583 Suggest update on play store instead of live 2026-01-19 13:49:18 +01:00
MartinBraquet
94298e4609 Fix options in multiple languages and filter language mismatch 2026-01-18 23:32:42 +01:00
MartinBraquet
274ea45cb8 Fix stringOrStringArrayToText 2026-01-18 23:01:01 +01:00
MartinBraquet
d5322e1863 Add missing translations for optional form 2026-01-18 22:48:02 +01:00
MartinBraquet
7131a5edaf Add translations for profile 2026-01-18 22:44:11 +01:00
MartinBraquet
ae977fbde7 Clean mobile filters 2026-01-18 22:34:08 +01:00
MartinBraquet
f68321690a Add matele reel 2026-01-18 20:40:45 +01:00
Martin Braquet
016841d6c2 Update press.tsx 2026-01-17 10:42:40 +01:00
MartinBraquet
635538b640 Center button text 2026-01-16 14:53:05 +01:00
MartinBraquet
cf6989fcd7 Clean /about buttons 2026-01-16 14:37:16 +01:00
MartinBraquet
9e324150c4 Disable sidebar open on swipe for now 2026-01-16 14:29:48 +01:00
MartinBraquet
f6bf4b5b23 Improve button look 2026-01-16 14:04:12 +01:00
MartinBraquet
111f8809ca Clean profile grid 2026-01-16 13:40:25 +01:00
MartinBraquet
548c1d3ad9 Move include short bios 2026-01-15 22:27:18 +01:00
MartinBraquet
c39dddf1db Clean /organization 2026-01-15 22:23:57 +01:00
MartinBraquet
39d856f368 Speed ISR for not found 2026-01-15 22:18:16 +01:00
MartinBraquet
b2c4e46180 Use locale to infer user spoken language 2026-01-15 20:29:05 +01:00
MartinBraquet
7622e864cb Reload page after sign up 2026-01-15 20:22:34 +01:00
MartinBraquet
bc66c0334a Revert "Reload page after sign up"
This reverts commit babef5f032.
2026-01-15 20:06:47 +01:00
MartinBraquet
babef5f032 Reload page after sign up 2026-01-15 20:00:24 +01:00
MartinBraquet
f7f67b0ab0 Add press info to FAQ 2026-01-15 19:53:22 +01:00
MartinBraquet
9219671430 Ask to reload 2026-01-15 19:52:19 +01:00
MartinBraquet
a4675246b2 Wait for 5 sec after profile creation 2026-01-15 19:52:01 +01:00
MartinBraquet
e2d78722d8 Ask to reload 2026-01-15 19:51:44 +01:00
MartinBraquet
d5c2f06784 Add Matele reportage to /press 2026-01-15 19:41:59 +01:00
MartinBraquet
26d9851c9e Merge remote-tracking branch 'origin/main' 2026-01-15 16:57:38 +01:00
MartinBraquet
fbfb959de2 Release 2026-01-15 16:57:26 +01:00
Okechi Jones-Williams
449b32d4bc Adding/Updating API unit tests (#26) 2026-01-15 16:54:29 +01:00
MartinBraquet
a3c479ff92 Release live update: Fix profile not found upon creation 2026-01-15 16:42:55 +01:00
MartinBraquet
51c97adce4 Fix profile not found upon creation 2026-01-15 16:32:21 +01:00
MartinBraquet
cde8a0d97f Add /press page 2026-01-15 16:07:39 +01:00
MartinBraquet
e257a10fdb Add bottom margin 2026-01-15 16:06:59 +01:00
MartinBraquet
6df9c5519a Translate 404 2026-01-15 16:06:42 +01:00
MartinBraquet
33a50b7e7e Update FAQ about android app 2026-01-12 11:36:59 +01:00
MartinBraquet
5029af9c5f Android release 2026-01-08 12:53:59 +02:00
Okechi Jones-Williams
7b61c70f6d Add more API endoints unit tests (#25)
* setting up test structure

* .

* added playwright config file, deleted original playwright folder and moved "some.test" file

* continued test structure setup

* Updating test folder structure

* Added database seeding script and backend testing folder structure

* removed the database test

* Replaced db seeding script

* Updated userInformation.ts to use values from choices.tsx

* merge prep

* removing extra unit test, moving api test to correct folder

* Pushing to get help with sql Unit test

* Updating get-profiles unit tests

* Added more unit tests

* .

* Added more unit tests

* Added getSupabaseToken unit test

* .

* excluding supabase token test so ci can pass

* .

* Seperated the seedDatabase func into its own file so it can be accessed seperatly

* Fixed failing test

* .

* .

* Fix tests

* Fix lint

* Clean

* Fixed module paths in compute-score unit test

* Updated root tsconfig to recognise backend/shared

* Added create comment unit test

* Added some unit tests

* Working on createProfile return issue

* .

* Fixes

* Updated Create profile unit test

* Updating create user unit test

* Add create-user unit tests

* .

* Added more unit tests

* Added more unit tests

* .

* Apply suggestion from @MartinBraquet

* .

* Added unit tests

* Added unit tests

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2026-01-08 12:52:49 +02:00
Okechi Jones-Williams
87059494a3 Add unit tests for continue in /create-profile (#24)
* setting up test structure

* .

* added playwright config file, deleted original playwright folder and moved "some.test" file

* continued test structure setup

* Updating test folder structure

* Added database seeding script and backend testing folder structure

* removed the database test

* Replaced db seeding script

* Updated userInformation.ts to use values from choices.tsx

* merge prep

* removing extra unit test, moving api test to correct folder

* Pushing to get help with sql Unit test

* Updating get-profiles unit tests

* Added more unit tests

* .

* Added more unit tests

* Added getSupabaseToken unit test

* .

* excluding supabase token test so ci can pass

* .

* Seperated the seedDatabase func into its own file so it can be accessed seperatly

* Fixed failing test

* .

* .

* Fix tests

* Fix lint

* Clean

* Fixed module paths in compute-score unit test

* Updated root tsconfig to recognise backend/shared

* Added create comment unit test

* Added some unit tests

* Working on createProfile return issue

* .

* Fixes

* Updated Create profile unit test

* Apply suggestion from @MartinBraquet

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2026-01-06 13:04:07 +02:00
Martin Braquet
e7f348e34e Update capawesome.json 2026-01-05 10:17:47 +01:00
Martin Braquet
f953b5c10b Update development.md 2026-01-05 10:10:26 +01:00
Martin Braquet
d75f179a46 Update de.json 2026-01-05 09:43:53 +01:00
Martin Braquet
87713a7803 Update README.md 2026-01-05 09:38:43 +01:00
MartinBraquet
ac7091ae06 Fix e2e 2026-01-04 13:58:18 +02:00
MartinBraquet
f4f3aa80f1 Fix missing translation for has kids 2026-01-04 13:58:11 +02:00
MartinBraquet
fa82a30907 Fix e2e tests 2026-01-04 13:48:13 +02:00
MartinBraquet
2e81ef25f1 Check that all keys are translated in german 2026-01-04 13:28:10 +02:00
MartinBraquet
12ef6a891b FInish german translation 2026-01-04 13:27:32 +02:00
MartinBraquet
fb3f5e5ace Add german translation 2026-01-04 13:03:23 +02:00
MartinBraquet
9537500fe1 Fix conditional hooks 2026-01-04 12:51:23 +02:00
MartinBraquet
bb423ba0e4 Translate filters 2026-01-04 12:42:53 +02:00
MartinBraquet
21db5cb481 Add docs for translation 2026-01-03 14:09:52 +02:00
MartinBraquet
078425f1b2 Translate search and save buttons 2026-01-03 13:50:15 +02:00
MartinBraquet
77e3b56b65 Translate search 2026-01-03 13:48:24 +02:00
MartinBraquet
2ed23e05fa Translate profile grid 2026-01-03 13:38:09 +02:00
MartinBraquet
486a4a81f7 Fix compat translation 2026-01-03 13:25:34 +02:00
MartinBraquet
9a0f0c0892 Translate profile forms 2026-01-03 13:18:21 +02:00
MartinBraquet
4abed529d3 Translate compat prompts 2026-01-02 19:23:12 +02:00
MartinBraquet
4293a8c24b Translate endorsements 2026-01-02 18:42:24 +02:00
MartinBraquet
60721eefb2 Translate profile bio 2026-01-02 18:19:28 +02:00
MartinBraquet
e5c8650df0 Translate profile-about.tsx to french 2026-01-02 18:11:29 +02:00
MartinBraquet
8e6d6584ea Add profile about translation features 2026-01-02 17:42:56 +02:00
MartinBraquet
077f3ac1a3 Fix 2026-01-02 15:44:37 +02:00
MartinBraquet
04b8e21769 Translate compat questions 2026-01-02 15:38:34 +02:00
MartinBraquet
bc672db79a Translate some profile blocks 2026-01-02 15:13:51 +02:00
MartinBraquet
dc7be2d334 Translate comment input 2026-01-02 15:10:58 +02:00
MartinBraquet
08c9f60010 Translate profiles 2026-01-02 15:05:27 +02:00
MartinBraquet
0bb52e72f7 Translate messages 2026-01-02 14:59:53 +02:00
MartinBraquet
593c2ac024 Translate about settings 2026-01-02 14:46:38 +02:00
MartinBraquet
8712424b89 Translate theme and notif settings 2026-01-02 14:42:16 +02:00
MartinBraquet
c408d895b1 Translate vote statuses 2026-01-02 14:25:10 +02:00
MartinBraquet
33c2121c8d Translate /referrals 2026-01-02 14:22:46 +02:00
MartinBraquet
fa151e79d3 Translate /stats charts 2026-01-02 14:15:19 +02:00
MartinBraquet
58e65baa47 Fix hydration 2026-01-02 14:15:02 +02:00
MartinBraquet
7bc57d8380 Fix sign in translation 2026-01-02 14:08:25 +02:00
MartinBraquet
eed3e71113 Translate vote sorting 2026-01-02 13:55:49 +02:00
MartinBraquet
08d98468c5 Translate vote components 2026-01-02 13:45:53 +02:00
MartinBraquet
0ee66a264e Translate /news 2026-01-02 13:37:11 +02:00
MartinBraquet
c10f8ceb91 Fix 2025-12-29 13:22:01 +02:00
MartinBraquet
e3cb85271c Translate /contact 2025-12-28 11:56:58 +02:00
MartinBraquet
2c43f3e1e7 Translate /security, /terms, /social, and /help pages 2025-12-28 11:54:40 +02:00
MartinBraquet
29ea3a600a Translate /stats 2025-12-28 11:43:27 +02:00
MartinBraquet
3ace0e80e8 Translate /profile, /privacy, /organization, and /notifications pages 2025-12-28 11:31:38 +02:00
MartinBraquet
79f460b9a2 Translate /compatibility, /delete-account and /donate 2025-12-28 11:24:48 +02:00
MartinBraquet
7d41846b0a Translate /register 2025-12-28 11:20:38 +02:00
MartinBraquet
d8997f64cb Translate /signin and /register 2025-12-28 11:13:33 +02:00
MartinBraquet
8a9139633d Translate /vote 2025-12-27 13:06:49 +02:00
MartinBraquet
a5191b440e Remove /md 2025-12-27 13:06:22 +02:00
MartinBraquet
c7d58905b5 Translate .md files 2025-12-27 12:51:47 +02:00
MartinBraquet
b6df79b836 Translate contact 2025-12-27 12:50:48 +02:00
MartinBraquet
9975113eff Translate about 2025-12-27 12:50:40 +02:00
MartinBraquet
e8431845b1 Fix translation bug in nav bars 2025-12-27 11:00:55 +02:00
MartinBraquet
a3e51f06e3 Add language picker when signed out and new badge 2025-12-27 10:00:17 +02:00
MartinBraquet
ce447db6b5 Translate nav bars 2025-12-26 19:56:23 +02:00
MartinBraquet
8c14212e10 Live update locale 2025-12-26 19:23:09 +02:00
MartinBraquet
9976e085c1 Add locale mem cache 2025-12-26 19:20:45 +02:00
MartinBraquet
6da973dd0c Clean 2025-12-26 19:16:34 +02:00
MartinBraquet
1dcf86e5ba Translate /home 2025-12-26 19:16:04 +02:00
MartinBraquet
3232e783e3 Remove forced server side rendering 2025-12-26 19:15:44 +02:00
MartinBraquet
c66d82a06d Fix 2025-12-26 18:53:30 +02:00
MartinBraquet
a46ff44f99 Add multi-lingual support 2025-12-26 18:45:53 +02:00
MartinBraquet
669a95bfa9 Fix sidebar swipe not working on webview 2025-12-26 16:13:52 +02:00
MartinBraquet
4287fbae85 Android live update 2025-12-26 14:17:28 +02:00
MartinBraquet
f2845eab91 Add open / close mobile sidebar on left / right swipe 2025-12-26 14:09:37 +02:00
MartinBraquet
40702e7832 Add android app link to google play 2025-12-21 01:06:01 +02:00
MartinBraquet
9909bd41cb Android prod release (finally) 2025-12-21 00:38:06 +02:00
MartinBraquet
0dec7e1987 Add Swahili 2025-12-16 12:06:40 +02:00
MartinBraquet
c5965fff89 Fix 2025-12-15 21:44:28 +02:00
MartinBraquet
53a1b4c415 Fix 2025-12-15 21:43:42 +02:00
MartinBraquet
773551e41d Fix 2025-12-15 21:40:03 +02:00
MartinBraquet
2df0e3df88 Fix 2025-12-15 21:38:58 +02:00
MartinBraquet
df39c2ee70 Fix 2025-12-15 21:38:07 +02:00
MartinBraquet
0bfd4d47b9 Fix 2025-12-15 21:34:49 +02:00
MartinBraquet
097617cfa0 Fix 2025-12-15 21:33:31 +02:00
MartinBraquet
b2968166df Fix 2025-12-15 21:31:55 +02:00
MartinBraquet
9116144de4 Fix 2025-12-15 21:30:58 +02:00
MartinBraquet
47e5e8bb28 Fix 2025-12-15 21:28:37 +02:00
MartinBraquet
32cae16045 Fix 2025-12-15 21:26:04 +02:00
MartinBraquet
4e4c946acf Improve issue templates 2025-12-15 21:24:04 +02:00
MartinBraquet
3e06b61eba Failed attempt to increase api docs font size on mobile 2025-12-15 18:39:31 +02:00
MartinBraquet
04e2376829 Increase version 2025-12-15 16:58:57 +02:00
MartinBraquet
709ef3f4ad Clean live update info 2025-12-15 16:58:42 +02:00
MartinBraquet
2e7de541aa Fix live update 2025-12-15 16:51:57 +02:00
MartinBraquet
dc073ef0f9 Fix live update 2025-12-15 16:51:47 +02:00
MartinBraquet
a677df2fdd Add live update info 2025-12-15 16:49:15 +02:00
MartinBraquet
58c005194c Clean 2025-12-15 15:59:00 +02:00
MartinBraquet
16a5c3e408 Clean 2025-12-15 15:58:23 +02:00
MartinBraquet
8a4fb040e1 OTA 2025-12-15 14:52:18 +02:00
MartinBraquet
eab140e51d Fix 2025-12-15 14:51:27 +02:00
MartinBraquet
009d4bf91f Fix 2025-12-15 14:44:40 +02:00
MartinBraquet
ed094bbeca Add backend commit message 2025-12-15 13:45:07 +02:00
MartinBraquet
704bcb4619 Increase API docs font size on mobile 2025-12-15 13:38:09 +02:00
MartinBraquet
fbdc594fa9 Expose version in /health 2025-12-15 12:41:58 +02:00
MartinBraquet
6a511adcf5 Fix cond hook 2025-12-15 12:33:39 +02:00
MartinBraquet
d55b04d22d Update about settings 2025-12-15 12:29:09 +02:00
MartinBraquet
96d0c90f8c Increase version 2025-12-15 12:28:59 +02:00
MartinBraquet
2253a734b1 Fix 2025-12-15 10:52:39 +02:00
MartinBraquet
dca08a6d81 Clean 2025-12-15 10:49:21 +02:00
MartinBraquet
8a4dc44fbc Show build info 2025-12-15 10:22:05 +02:00
MartinBraquet
e231f016d6 Clean 2025-12-15 00:16:04 +02:00
MartinBraquet
05250285c0 Clean 2025-12-15 00:14:49 +02:00
MartinBraquet
ce9ac99894 Update FAQ 2025-12-15 00:11:05 +02:00
MartinBraquet
f76c39bd1c Fix 2025-12-15 00:05:41 +02:00
MartinBraquet
9ccb8d002b Fix 2025-12-15 00:01:13 +02:00
MartinBraquet
d112d4e739 Fix 2025-12-15 00:01:00 +02:00
MartinBraquet
7dfec75ac0 Fix 2025-12-14 23:54:41 +02:00
MartinBraquet
3fd27131e2 Fix 2025-12-14 23:50:24 +02:00
MartinBraquet
355fd7e6d1 Fix 2025-12-14 23:43:46 +02:00
MartinBraquet
c0b8df4ef9 Fix 2025-12-14 23:39:48 +02:00
MartinBraquet
cc3af74676 Fix 2025-12-14 23:27:45 +02:00
MartinBraquet
40a1e079a1 Fix 2025-12-14 23:27:19 +02:00
MartinBraquet
764769366c Fix 2025-12-14 23:25:33 +02:00
MartinBraquet
b433668628 Fix 2025-12-14 23:24:55 +02:00
MartinBraquet
cf27794776 Fix 2025-12-14 23:20:46 +02:00
MartinBraquet
238fed617f Fix 2025-12-14 23:19:02 +02:00
MartinBraquet
0d026d36d1 Add CD for android live update 2025-12-14 23:05:23 +02:00
MartinBraquet
d699ceae38 Release 2025-12-13 10:55:28 +02:00
MartinBraquet
762dd93042 Merge remote-tracking branch 'origin/main' 2025-12-12 10:50:39 +02:00
MartinBraquet
1f5ed87363 Add issue and PR template 2025-12-12 10:50:21 +02:00
Okechi Jones-Williams
28ce878b34 Add API unit tests for create-* endpoints (#23)
* setting up test structure

* .

* added playwright config file, deleted original playwright folder and moved "some.test" file

* continued test structure setup

* Updating test folder structure

* Added database seeding script and backend testing folder structure

* removed the database test

* Replaced db seeding script

* Updated userInformation.ts to use values from choices.tsx

* merge prep

* removing extra unit test, moving api test to correct folder

* Pushing to get help with sql Unit test

* Updating get-profiles unit tests

* Added more unit tests

* .

* Added more unit tests

* Added getSupabaseToken unit test

* .

* excluding supabase token test so ci can pass

* .

* Seperated the seedDatabase func into its own file so it can be accessed seperatly

* Fixed failing test

* .

* .

* Fix tests

* Fix lint

* Clean

* Fixed module paths in compute-score unit test

* Updated root tsconfig to recognise backend/shared

* Added create comment unit test

* Added some unit tests

* Working on createProfile return issue

* .

* Fixes

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2025-12-12 10:34:07 +02:00
MartinBraquet
be2e19db8d Cover cleanDocs with tests 2025-12-11 20:42:39 +01:00
MartinBraquet
785a633115 Fix: trim hardbreak only at start and end 2025-12-11 20:15:53 +01:00
MartinBraquet
57beefb894 Fix query collision on i variable 2025-12-09 19:08:16 +01:00
MartinBraquet
787d35e057 Merge remote-tracking branch 'origin/main' 2025-12-08 12:16:11 +01:00
MartinBraquet
a5b1d1abb0 Improve RelationshipStatus icon 2025-12-08 12:16:04 +01:00
Martin Braquet
daa517a11d Update dev-rules.mdc to include project structure 2025-12-07 12:33:25 +01:00
Nicholas Chamberlain
ef7665c7da Add firebase emulator, Add registration script, Add signup spec (#22)
* add firebase emulator, add registration script, add signup spec

* Upgrade firebase emulator and make it pass the E2E tests

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2025-12-06 23:43:46 +01:00
MartinBraquet
348a557f5c Clean 2025-12-05 16:53:33 +01:00
MartinBraquet
26b36ab1c8 Test 2025-12-05 16:47:14 +01:00
MartinBraquet
94faa30882 Fix 2025-12-05 00:53:53 +01:00
MartinBraquet
c579b3ac15 Upgrade 2025-12-05 00:53:18 +01:00
MartinBraquet
82bf346ce5 Improve readme 2025-12-05 00:50:03 +01:00
MartinBraquet
67ed9d0d8e Restore 2025-12-05 00:46:34 +01:00
MartinBraquet
c0b241e70a Test 2025-12-05 00:35:54 +01:00
MartinBraquet
808acd8289 Todo done 2025-12-04 21:52:09 +01:00
MartinBraquet
3d39fe994d Add 2025-12-04 21:45:53 +01:00
MartinBraquet
569db46a8b Remove add-iam-policy-binding to roles/artifactregistry.reader post API deploy
May not be needed
2025-12-04 21:44:55 +01:00
MartinBraquet
9493ee65cf Fix 2025-12-04 21:20:01 +01:00
MartinBraquet
ce962f60ff Fix 2025-12-04 21:19:06 +01:00
MartinBraquet
922232e943 Fix 2025-12-04 21:17:47 +01:00
MartinBraquet
ca587ce962 Fix 2025-12-04 21:17:01 +01:00
MartinBraquet
755be6e0e0 Fix 2025-12-04 21:16:11 +01:00
MartinBraquet
4a64e5f0a0 Fix 2025-12-04 21:12:12 +01:00
MartinBraquet
c458b42821 Fix 2025-12-04 21:09:00 +01:00
MartinBraquet
dc298c4f46 Fix 2025-12-04 21:04:01 +01:00
MartinBraquet
91f69ed928 Fix 2025-12-04 20:51:09 +01:00
MartinBraquet
64c04e2d23 Fix 2025-12-04 20:51:02 +01:00
MartinBraquet
ae8077d700 Increment API version 2025-12-04 20:46:47 +01:00
MartinBraquet
50d8e6388e Add auto API deploy 2025-12-04 20:45:54 +01:00
MartinBraquet
af334c7142 Undo 2025-12-04 20:39:20 +01:00
MartinBraquet
f2da0bd58a Undo 2025-12-04 20:39:12 +01:00
MartinBraquet
cfdea26599 git commit -m "Stop tracking metadata.json" 2025-12-04 20:38:14 +01:00
MartinBraquet
a4a6abec72 Fix 2025-12-04 20:36:32 +01:00
MartinBraquet
d8dfd6e093 Stop tracking metadata.json 2025-12-04 20:34:52 +01:00
MartinBraquet
5175b2ca15 Add git info to backend API 2025-12-04 20:31:45 +01:00
MartinBraquet
f734772670 Add 2025-12-04 20:18:25 +01:00
MartinBraquet
5eee62f731 Fix 2025-12-04 19:58:39 +01:00
MartinBraquet
4f8d76f797 Add description below profile images 2025-12-04 19:51:04 +01:00
MartinBraquet
bf4acc09fb Clean todo 2025-12-03 23:55:03 +01:00
MartinBraquet
2a3a39d6f7 Move to config folder 2025-12-03 23:51:41 +01:00
MartinBraquet
92b1ffd61c Remove old folder 2025-12-03 23:44:42 +01:00
MartinBraquet
c4c9316386 Fix tests 2025-12-03 23:32:52 +01:00
MartinBraquet
0509ff8fac Add new badge 2025-12-03 23:23:53 +01:00
MartinBraquet
1dc85e25de Fix 2025-12-03 17:00:48 +01:00
MartinBraquet
fb695bbed1 Fix 2025-12-03 16:59:25 +01:00
MartinBraquet
3b0465c65c Add profile fields: work areas, moral causes, and interests 2025-12-03 16:56:02 +01:00
MartinBraquet
43238ecc44 Clean comment 2025-12-01 19:19:01 +01:00
MartinBraquet
821d280f5c Refresh profile pics and make them square 2025-12-01 16:09:35 +01:00
MartinBraquet
1da487a972 Fix 2025-11-30 16:21:53 +01:00
MartinBraquet
61c53301bf Load compat questions progressively to avoid long page load 2025-11-30 13:15:29 +01:00
MartinBraquet
61613af1b7 Hide logs 2025-11-30 13:13:53 +01:00
Okechi Jones-Williams
ab612a3eca Add backend API unit tests (#21)
* setting up test structure

* .

* added playwright config file, deleted original playwright folder and moved "some.test" file

* continued test structure setup

* Updating test folder structure

* Added database seeding script and backend testing folder structure

* removed the database test

* Replaced db seeding script

* Updated userInformation.ts to use values from choices.tsx

* merge prep

* removing extra unit test, moving api test to correct folder

* Pushing to get help with sql Unit test

* Updating get-profiles unit tests

* Added more unit tests

* .

* Added more unit tests

* Added getSupabaseToken unit test

* .

* excluding supabase token test so ci can pass

* .

* Seperated the seedDatabase func into its own file so it can be accessed seperatly

* Fixed failing test

* .

* .

* Fix tests

* Fix lint

* Clean

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2025-11-30 00:03:16 +01:00
MartinBraquet
f323034eed Clean 2025-11-26 23:11:55 +01:00
MartinBraquet
aa35fa3b2b Pre compute compatibility scores for faster profile lookup 2025-11-26 22:49:33 +01:00
MartinBraquet
f97d24402e Update todo 2025-11-26 16:33:10 +01:00
MartinBraquet
036776bde8 Add search results count in profiles grid 2025-11-26 16:15:39 +01:00
MartinBraquet
63e48d99ca Do not show compat score if one hasn't answered any question 2025-11-26 15:19:28 +01:00
MartinBraquet
4dd6f54b37 Add compat questions if none answered 2025-11-25 20:44:05 +01:00
MartinBraquet
a4e02031c6 Add prompts category 2025-11-25 15:03:34 +01:00
MartinBraquet
95bdc37411 Clean 2025-11-25 14:34:06 +01:00
MartinBraquet
c90b617ad0 Fix typo 2025-11-23 17:09:18 +01:00
MartinBraquet
aecdaa2875 Clean home 2025-11-23 16:09:30 +01:00
MartinBraquet
7e924c2741 Release 2025-11-23 14:22:54 +01:00
MartinBraquet
241b851c02 Fix 2025-11-20 21:13:38 +01:00
MartinBraquet
2ba9949035 Add get it on google play 2025-11-20 21:09:42 +01:00
MartinBraquet
3d4b76ffc3 Fix message fetching after sending 2025-11-19 21:04:08 +01:00
Nicholas Chamberlain
f7fb0c6c82 Test/e2e login auth (#19)
* Add basic test for login and auth state

* Remove test file

* Change login setup

* Apply suggestions from code review

Co-authored-by: Martin Braquet <martin.braquet@gmail.com>

* Change signin structure to use UI

* Fix URL loading

* Spin up backend server as well for E2E

---------

Co-authored-by: Martin Braquet <martin.braquet@gmail.com>
2025-11-18 23:17:00 +01:00
MartinBraquet
e5fc734b90 Fix question refresh too quick 2025-11-15 16:32:55 +01:00
MartinBraquet
10fa659e52 Add favicon.svg 2025-11-15 16:18:27 +01:00
MartinBraquet
0ac315b017 Merge remote-tracking branch 'origin/main' 2025-11-15 16:12:36 +01:00
MartinBraquet
bbfbd2daae Remove 2025-11-15 16:04:16 +01:00
MartinBraquet
cd2c4d3314 Load favicon from local webview assets 2025-11-15 15:55:01 +01:00
MartinBraquet
37ee7752c2 Fix build-web-view 2025-11-15 15:53:07 +01:00
Okechi Jones-Williams
6b11e6b060 Add minor changes to tests (#20)
* setting up test structure

* .

* added playwright config file, deleted original playwright folder and moved "some.test" file

* continued test structure setup

* Updating test folder structure

* Added database seeding script and backend testing folder structure

* removed the database test

* Replaced db seeding script

* Updated userInformation.ts to use values from choices.tsx

* merge prep

* removing extra unit test, moving api test to correct folder
2025-11-15 15:35:45 +01:00
MartinBraquet
f650ab7394 Clean 2025-11-15 14:20:14 +01:00
MartinBraquet
fead7459d4 Clean 2025-11-15 14:12:06 +01:00
MartinBraquet
bbf3970121 Update todo 2025-11-15 14:06:15 +01:00
MartinBraquet
26fb810840 Add docs links 2025-11-15 14:01:45 +01:00
MartinBraquet
af9074af6e Add users link to stats 2025-11-15 13:53:35 +01:00
MartinBraquet
4229c2a4fa Merge remote-tracking branch 'origin/main' 2025-11-15 13:52:14 +01:00
MartinBraquet
fd58602e6d Add "core" 2025-11-15 13:52:05 +01:00
MartinBraquet
af26397ad7 Rounded dropdown 2025-11-15 13:51:58 +01:00
MartinBraquet
859d01594a Allow for skipping unanswered questions in /compatibility 2025-11-15 13:51:48 +01:00
MartinBraquet
09a37058e6 Fix tabs index caching 2025-11-15 13:22:48 +01:00
MartinBraquet
edc7366b1d Improve docs 2025-11-15 13:05:26 +01:00
MartinBraquet
7306cb335b Add chalk 2025-11-15 12:24:06 +01:00
Martin Braquet
1e13cc4294 Update README.md 2025-11-15 10:16:44 +01:00
Martin Braquet
a88e5a9ec8 Update README.md 2025-11-15 09:20:46 +01:00
MartinBraquet
09d743c603 Fix 2025-11-15 01:04:41 +01:00
MartinBraquet
36c1ec528a Include all ts/tsx files in coverage 2025-11-15 00:52:37 +01:00
MartinBraquet
aec9600036 Clean 2025-11-15 00:48:37 +01:00
MartinBraquet
6e1306bdd6 Merge coverage files 2025-11-15 00:41:48 +01:00
MartinBraquet
37f5c95716 Fix coverage 2025-11-15 00:33:25 +01:00
MartinBraquet
0d48c541a0 Add tests runs to all packages 2025-11-15 00:27:30 +01:00
MartinBraquet
8928cd1667 Update test structure 2025-11-15 00:14:12 +01:00
MartinBraquet
780f935fea Fix webview live update 2025-11-14 18:53:25 +01:00
MartinBraquet
5bf095178d Add live updates for the webview app 2025-11-14 18:12:29 +01:00
MartinBraquet
e135293b43 Lowercase new 2025-11-14 18:12:05 +01:00
MartinBraquet
be7e009909 Fix 2025-11-14 16:03:01 +01:00
MartinBraquet
ada8a713c1 Fix 2025-11-14 16:01:21 +01:00
MartinBraquet
eb7391dae0 Skip for now 2025-11-14 15:24:19 +01:00
MartinBraquet
8bdbd5e4fe Add code coverage 2025-11-14 15:23:56 +01:00
MartinBraquet
137d15ae71 Clean 2025-11-14 14:37:01 +01:00
MartinBraquet
8a2ed6f8ff Improve docs 2025-11-14 14:32:30 +01:00
MartinBraquet
7766b43187 Fix 2025-11-14 14:14:52 +01:00
MartinBraquet
b9a637fdac Fix 2025-11-14 14:13:34 +01:00
MartinBraquet
3096dbc922 Add Next.js.md 2025-11-14 14:12:07 +01:00
MartinBraquet
d6b0bb4378 Ignore getServerSideProps on mobile 2025-11-14 12:26:37 +01:00
MartinBraquet
43ef43ba72 Little fixes 2025-11-13 20:38:16 +01:00
MartinBraquet
314037dd06 Add /compatibility page to browse all the questions 2025-11-13 20:10:53 +01:00
MartinBraquet
7c4d66bbf5 Fix 2025-11-13 16:27:58 +01:00
MartinBraquet
49d28961ef Improve UI for filters 2025-11-13 15:53:39 +01:00
MartinBraquet
9d649daee5 Add profile field and filter: MBTI 2025-11-13 15:12:11 +01:00
MartinBraquet
7598a47283 Add local logging 2025-11-13 14:11:50 +01:00
MartinBraquet
c9f7230d27 Fix 2025-11-13 13:54:23 +01:00
MartinBraquet
716633c6df Remove trailing empty paragraphs 2025-11-13 13:53:11 +01:00
MartinBraquet
8a215a765f Add AI assistant rules 2025-11-13 13:13:17 +01:00
MartinBraquet
e2ff41a0b1 Ignore 2025-11-13 13:12:57 +01:00
MartinBraquet
d790fae74a Add e2e script for local development 2025-11-13 13:11:53 +01:00
MartinBraquet
1a17862f45 Clean choice constants 2025-11-12 18:39:56 +01:00
MartinBraquet
acd4c36531 Add profile field and filter: relationship status 2025-11-12 18:29:28 +01:00
MartinBraquet
f623450f08 Clean 2025-11-11 22:13:07 +01:00
MartinBraquet
ce681cfb67 Add profile field and filter: languages 2025-11-11 22:02:16 +01:00
MartinBraquet
a0a6523a25 Move stats up 2025-11-11 21:07:51 +01:00
MartinBraquet
8e2fa36d0e Fix 2025-11-11 21:07:40 +01:00
MartinBraquet
c953a84c1f Clean choices 2025-11-11 21:06:56 +01:00
MartinBraquet
2b403f0761 Move log 2025-11-11 21:06:44 +01:00
Okechi Jones-Williams
f954e3b2d7 Add database seeding script and backend testing folder structure (#18)
* setting up test structure

* added playwright config file, deleted original playwright folder and moved "some.test" file

* continued test structure setup

* Updating test folder structure

* Added database seeding script and backend testing folder structure

* removed the database test

* Replaced db seeding script

* Updated userInformation.ts to use values from choices.tsx
2025-11-11 19:00:07 +01:00
MartinBraquet
24ee2a206e Back to up 2025-11-10 18:59:15 +01:00
MartinBraquet
02d165829f Move to / 2025-11-10 18:54:09 +01:00
MartinBraquet
b4d996bd14 Fix 2025-11-10 18:49:22 +01:00
MartinBraquet
60989faa03 Add TEST_DOWNTIME 2025-11-10 18:45:07 +01:00
MartinBraquet
4aeda8a1a7 Clean 2025-11-09 22:13:01 +01:00
MartinBraquet
023a20f263 Add script to add test user to db 2025-11-09 20:20:22 +01:00
MartinBraquet
62821ed803 Release 2025-11-08 20:52:41 +01:00
MartinBraquet
903d62ed57 Add error comment 2025-11-08 19:55:13 +01:00
MartinBraquet
adef626b34 Cache build ID 2025-11-08 19:46:42 +01:00
MartinBraquet
11a933cc04 Comment 2025-11-08 19:46:21 +01:00
MartinBraquet
4b5ce99bb1 Fix avatar link not working on mobile 2025-11-08 19:46:13 +01:00
MartinBraquet
f8dff77cee Do not use ID filtering anymore as loaded messages may be deleted or edited 2025-11-08 18:50:23 +01:00
MartinBraquet
0a9b08803e Add comment 2025-11-08 18:48:47 +01:00
MartinBraquet
39a8568663 Fix orderBy that must be filtered out 2025-11-08 18:12:13 +01:00
MartinBraquet
e1502440eb Increase debounce since moved back to free plan (1 / sec) 2025-11-07 23:31:18 +01:00
MartinBraquet
5834d032c3 Android release 2025-11-07 23:26:46 +01:00
MartinBraquet
0a28a2af61 Allow editing old messages 2025-11-07 23:26:38 +01:00
MartinBraquet
259f56bd26 Fix typescript / linting warnings 2025-11-07 23:17:16 +01:00
MartinBraquet
011ad66a3f Fix emoji location (2) 2025-11-07 23:13:32 +01:00
MartinBraquet
281c72f88d Merge remote-tracking branch 'origin/main' 2025-11-07 22:53:07 +01:00
Okechi Jones-Williams
1293523ebf Added further playwright structure (#17)
* setting up test structure

* .

* added playwright config file, deleted original playwright folder and moved "some.test" file

* continued test structure setup

* Updating test folder structure
2025-11-07 22:52:19 +01:00
MartinBraquet
67e5d34f39 Fix emoji location 2025-11-07 22:48:38 +01:00
MartinBraquet
8b3dec6116 Merge remote-tracking branch 'origin/main' 2025-11-07 21:52:25 +01:00
MartinBraquet
3359f49f0a Add edit, delete and emojis on messages 2025-11-07 21:51:56 +01:00
Okechi Jones-Williams
9715cae587 Setting up test file structure (#16)
* setting up test structure

* .
2025-11-07 14:28:07 +01:00
MartinBraquet
4b122bd907 Increase version 2025-11-05 20:52:19 +01:00
MartinBraquet
edefa36c8a Comments 2025-11-05 20:46:56 +01:00
MartinBraquet
e40a352aed When clicking on the DM notif, go to the DM chat 2025-11-05 20:46:41 +01:00
MartinBraquet
70267f0623 Add demo fix
Hope it works finally
2025-11-04 17:51:38 +01:00
MartinBraquet
c19ed7f6d5 Add demo
Hope it works finally
2025-11-04 17:37:49 +01:00
MartinBraquet
e2fb0f2ee8 Line clamp notif to 5 2025-11-04 13:07:57 +01:00
MartinBraquet
f9e208a7e0 Add createAndroidTestNotifications 2025-11-04 13:04:06 +01:00
MartinBraquet
ee2b8a60a2 Increase notif lines 2025-11-04 13:03:43 +01:00
MartinBraquet
5651f17e96 Increase android version 2025-11-04 12:46:17 +01:00
MartinBraquet
9679b3722a Show error 2025-11-04 01:25:19 +01:00
MartinBraquet
6cb736a10d Upgrade 2025-11-03 23:55:37 +01:00
MartinBraquet
ac5c3b421d Upgrade 2025-11-03 23:45:53 +01:00
MartinBraquet
f01fad5fa6 Improve message rendering on webview (fix 2) 2025-11-03 23:45:03 +01:00
MartinBraquet
110b727cbc Improve message rendering on webview (fix) 2025-11-03 23:13:56 +01:00
MartinBraquet
1dba2debc5 Clean 2025-11-03 20:27:32 +01:00
MartinBraquet
8fec73341f Comment 2025-11-03 18:39:56 +01:00
MartinBraquet
33c76de397 Improve bio tips 2025-11-03 17:19:37 +01:00
MartinBraquet
d409eb1126 Refactor news 2025-11-03 16:53:50 +01:00
MartinBraquet
69d53591c8 Load cached remote page data from webview 2025-11-03 16:44:41 +01:00
MartinBraquet
4cf783f07f Add proxy 2025-11-03 16:19:10 +01:00
MartinBraquet
a971c121cc Allow any origin 2025-11-03 16:04:50 +01:00
MartinBraquet
b4abfb6aa4 Simplify build-id 2025-11-03 15:53:44 +01:00
MartinBraquet
8f760081a5 Add build-id/route.ts 2025-11-03 15:25:11 +01:00
MartinBraquet
c15934f210 Render home icon if logged out 2025-11-03 15:17:14 +01:00
MartinBraquet
c7e1bb4463 People is more human than profiles 2025-11-03 12:15:18 +01:00
MartinBraquet
4ac97fc476 Make webview go back one page instead of closing the app 2025-11-02 22:26:22 +01:00
MartinBraquet
a5dda01ffd Clean 2025-11-02 22:20:49 +01:00
MartinBraquet
9db4937e54 Fix layout 2025-11-02 22:07:56 +01:00
MartinBraquet
3dc50a384e Add title when loading 2025-11-02 22:05:36 +01:00
MartinBraquet
ccc48620b8 Fix profile page not switching between users 2025-11-02 21:41:23 +01:00
MartinBraquet
943c3960e1 Comment logs 2025-11-02 21:39:17 +01:00
MartinBraquet
9a84a67555 Improve margins for mobile 2025-11-02 21:29:51 +01:00
MartinBraquet
2241e3c7d3 Log pixel top and bottom 2025-11-02 21:27:07 +01:00
MartinBraquet
03042dae96 Do not disclose exact last online time for privacy reasons 2025-11-02 21:26:17 +01:00
MartinBraquet
3df9d067d6 Clean posthog 2025-11-02 21:15:09 +01:00
MartinBraquet
c5f3e8b3c2 Clean css 2025-11-02 20:26:39 +01:00
MartinBraquet
3a3e8c0e10 Add get pixel height 2025-11-02 20:26:29 +01:00
MartinBraquet
f010bf7eed Fix 2025-11-02 13:14:20 +01:00
MartinBraquet
b22d6a77b0 Update readme 2025-11-02 13:09:14 +01:00
MartinBraquet
f91d9125e4 Ignore getStaticProps and getStaticPaths for mobile webview build 2025-11-02 13:09:00 +01:00
MartinBraquet
625eedef1e Add middleware template 2025-11-02 13:04:10 +01:00
MartinBraquet
61dd33187a Use SSR/ISR only for web app (load client side on mobile app) 2025-11-02 13:03:22 +01:00
MartinBraquet
817640c7d0 Update reserved path 2025-11-02 00:21:25 +01:00
MartinBraquet
aa15d5a6df Comment 2025-11-02 00:21:15 +01:00
MartinBraquet
60857007bd Fix 2025-11-01 22:11:50 +01:00
MartinBraquet
cfd0c5d846 versionCode 4 2025-11-01 22:05:43 +01:00
MartinBraquet
71ec88d235 Add mobile icons 2025-11-01 22:05:28 +01:00
MartinBraquet
902ea583bd Remove AD_ID 2025-11-01 21:27:36 +01:00
MartinBraquet
fb6b54fba0 Delete mobile subscriptions if not set up 2025-11-01 21:26:43 +01:00
MartinBraquet
9dfc56987c Add Child Sexual Abuse and Exploitation (CSAE) 2025-11-01 20:30:42 +01:00
MartinBraquet
b982a02717 V2 2025-11-01 18:47:55 +01:00
MartinBraquet
15a0d8ee16 Add /delete-account 2025-11-01 17:20:31 +01:00
MartinBraquet
9ca03132db Rename to com.compassconnections.com 2025-11-01 17:14:08 +01:00
MartinBraquet
f908ba3ea3 Update terms 2025-11-01 17:09:15 +01:00
MartinBraquet
79ea09bd91 Skip web service-worker.js on native mobile 2025-11-01 16:25:55 +01:00
MartinBraquet
c743a4f1fe Comment unused google auth 2025-11-01 16:22:09 +01:00
MartinBraquet
ccd1fc5c10 Fix data not in notif 2025-11-01 16:06:22 +01:00
MartinBraquet
2f82a64dbe Add isNativeMobile 2025-11-01 16:06:07 +01:00
MartinBraquet
71c6eae9c4 Fix googleNativeLogin 2025-11-01 16:05:58 +01:00
MartinBraquet
28894a08cc Add capgo-capacitor-social-login to gradle 2025-11-01 16:05:42 +01:00
MartinBraquet
41e366dcc4 Output export for webview 2025-11-01 16:05:26 +01:00
MartinBraquet
6ee11a6b2d Skip getStaticProps in webview 2025-11-01 16:05:03 +01:00
MartinBraquet
ab82c66f83 Add bridge for social 2025-11-01 16:04:36 +01:00
MartinBraquet
f94820f45e Fix favicon not showing in webview 2025-11-01 16:03:39 +01:00
MartinBraquet
fe03e1ca68 Use local assets for prod 2025-11-01 16:03:26 +01:00
MartinBraquet
44ee6951c9 Comment usused deep link 2025-11-01 16:02:52 +01:00
MartinBraquet
40194f7204 Update readme 2025-11-01 16:02:37 +01:00
MartinBraquet
40eefef9a2 Use full ico path for webview 2025-11-01 16:02:25 +01:00
MartinBraquet
d8ab44ebb5 Install @capgo/capacitor-social-login 2025-11-01 16:01:47 +01:00
MartinBraquet
f875539f2e Move to unused 2025-11-01 16:01:36 +01:00
MartinBraquet
d9c2d142cb Format 2025-11-01 16:00:11 +01:00
MartinBraquet
51d5715f04 Clean 404 2025-11-01 12:38:01 +01:00
MartinBraquet
8ad81b1d50 Add bottom margin 2025-11-01 12:37:55 +01:00
MartinBraquet
6d4083d8a7 Move hosting constants to new file 2025-11-01 11:19:36 +01:00
MartinBraquet
bb120afea2 Clean web build 2025-11-01 11:17:20 +01:00
MartinBraquet
cebcc20c26 Add email verif link in welcome email 2025-11-01 10:53:10 +01:00
MartinBraquet
0522e787cd Add Forgot Password? 2025-10-31 20:41:57 +01:00
MartinBraquet
81e01f1485 Change tag 2025-10-31 20:32:09 +01:00
MartinBraquet
9b2b93d56f Update faq 2025-10-31 20:31:40 +01:00
MartinBraquet
133b402e2b Add IS_WEBVIEW_BUILD 2025-10-31 13:36:41 +01:00
MartinBraquet
9a91eab13f Catch user not found 2025-10-31 11:52:32 +01:00
MartinBraquet
5f6722b917 Add message notif 2025-10-31 11:52:24 +01:00
MartinBraquet
fdcd4d46ac Add allowNavigation 2025-10-31 11:52:06 +01:00
MartinBraquet
27a72170b8 Url redirect 2025-10-31 11:51:58 +01:00
MartinBraquet
b3021e60ec Add sync_android.sh 2025-10-31 10:41:22 +01:00
MartinBraquet
6d8834bd87 Add little emoji 2025-10-31 10:41:12 +01:00
MartinBraquet
ab8a9d95d8 Add notif perms 2025-10-31 00:15:42 +01:00
MartinBraquet
563ee3f5df Remove unused social login 2025-10-31 00:15:34 +01:00
MartinBraquet
c3389a7fcf Uncomment 2025-10-31 00:14:57 +01:00
MartinBraquet
e824bbb533 Add webview push notif 2025-10-30 23:45:04 +01:00
MartinBraquet
7de0e351f3 Skip notif verif in client 2025-10-30 23:44:41 +01:00
MartinBraquet
2df5f55390 Add androidpush check 2025-10-30 23:31:57 +01:00
MartinBraquet
21038cc5ac Clean and refactor oauth redirect 2025-10-30 23:28:18 +01:00
MartinBraquet
d6749bcd41 Add way to access profiles without log in during dev 2025-10-30 23:18:50 +01:00
MartinBraquet
005c9ccdef Add info 2025-10-30 23:18:31 +01:00
MartinBraquet
d47cb53e59 Fix filter top touching top nav bar 2025-10-30 23:18:18 +01:00
MartinBraquet
adfb3ca4f0 Add build_sync_android.sh 2025-10-30 23:03:16 +01:00
MartinBraquet
88b7e4edda Update android readmes 2025-10-30 22:59:09 +01:00
MartinBraquet
e33b57f0fd Comment sensitive logs 2025-10-30 22:52:33 +01:00
MartinBraquet
777825b73f Better log 2025-10-30 22:50:16 +01:00
MartinBraquet
04ca9b6f9a Remove PKCE as using google client secret 2025-10-30 22:39:10 +01:00
MartinBraquet
7f3d3eeb9c Hide background in bottom nav bar 2025-10-30 22:24:47 +01:00
MartinBraquet
75ac16d43c Rename 2025-10-30 21:59:33 +01:00
MartinBraquet
5f1120c718 Fix 2025-10-30 21:55:21 +01:00
MartinBraquet
806a0694c6 Add status bar 2025-10-30 21:50:36 +01:00
MartinBraquet
c506ae3242 Fix statusbard 2025-10-30 21:48:31 +01:00
MartinBraquet
444fa529fb Fix margin 2025-10-30 21:47:17 +01:00
MartinBraquet
62ced3eb04 Add top and bottom margin for mobile 2025-10-30 21:40:36 +01:00
MartinBraquet
31718f1c4d Refactor redirect url 2025-10-30 20:37:12 +01:00
MartinBraquet
98ab8971b4 Clean 2025-10-30 19:52:55 +01:00
MartinBraquet
870c86f9af Cleanup 2025-10-30 19:39:03 +01:00
MartinBraquet
a4f6aabee9 Cleanup 2025-10-30 19:37:11 +01:00
MartinBraquet
5584ad0a10 Remove unused cst 2025-10-30 19:26:26 +01:00
MartinBraquet
6b4932b4c5 Add error handling 2025-10-30 19:26:19 +01:00
MartinBraquet
7f1cb0aaf3 Add firebase login after receiving tokens 2025-10-30 19:26:08 +01:00
MartinBraquet
d42a5a48e9 Send code verifier to backend 2025-10-30 18:44:22 +01:00
MartinBraquet
8a1b762c35 Fix readme 2025-10-30 18:44:12 +01:00
MartinBraquet
d2d1de41d2 Fix 2025-10-30 18:08:22 +01:00
MartinBraquet
d1366af2a0 Fix 2025-10-30 18:06:14 +01:00
MartinBraquet
cae4b15bbb Fix 2025-10-30 18:01:49 +01:00
MartinBraquet
e41bc64b0c Fix 2025-10-30 17:21:19 +01:00
MartinBraquet
3d03ebe487 Fix 2025-10-30 17:18:00 +01:00
MartinBraquet
1fcb431d1b Fix bridge 2025-10-30 17:15:49 +01:00
MartinBraquet
4f321490af Fix 2025-10-30 17:11:53 +01:00
MartinBraquet
04c7469e68 Fix 2025-10-30 17:06:19 +01:00
MartinBraquet
98bc0a9309 Fix 2025-10-30 17:01:21 +01:00
MartinBraquet
6e537f4cdf Fix 2025-10-30 16:48:52 +01:00
MartinBraquet
b121d61852 Fix 2025-10-30 16:24:22 +01:00
MartinBraquet
6ba3c3ffbd Fix 2025-10-30 13:53:59 +01:00
MartinBraquet
67b3efad4c Fix 2025-10-30 13:50:05 +01:00
MartinBraquet
1282e468e3 Fix 2025-10-30 13:47:12 +01:00
MartinBraquet
67b2e78a63 Fix oauthRedirect 2025-10-30 13:44:40 +01:00
MartinBraquet
213c56f945 Fix 2025-10-30 13:20:27 +01:00
MartinBraquet
ccde6e4f4b Fix 2025-10-30 12:46:25 +01:00
MartinBraquet
2c1c94d24c Update android webview 2025-10-30 12:40:20 +01:00
MartinBraquet
56edb51f36 Add bridge 2025-10-30 12:40:00 +01:00
MartinBraquet
b6ed2c7dd8 Change GOOGLE_CLIENT_ID 2025-10-30 12:25:46 +01:00
MartinBraquet
74c7c5c423 Fix GOOGLE_CLIENT_ID 2025-10-30 12:00:04 +01:00
MartinBraquet
314d774bde Fix 2025-10-30 11:51:56 +01:00
MartinBraquet
d94091ae4e Add webviewGoogleSignin 2025-10-30 11:33:23 +01:00
MartinBraquet
8bf3c4fcd7 Remove sign up button if logged in 2025-10-29 20:43:36 +01:00
MartinBraquet
4358c15432 Update TODOS 2025-10-29 20:30:56 +01:00
MartinBraquet
fdf8d649fe Link logo to /home page 2025-10-29 20:04:50 +01:00
MartinBraquet
a4e22ec4b1 Add email change 2025-10-29 20:01:42 +01:00
MartinBraquet
783bc43547 Move delete pinned photo 2025-10-29 19:23:33 +01:00
MartinBraquet
2e6aec175a Comment 2025-10-29 17:51:22 +01:00
MartinBraquet
ee41aaa112 Use WithPrivateUser 2025-10-29 17:51:12 +01:00
MartinBraquet
32e8a8b1b9 Add donation name 2025-10-29 17:30:57 +01:00
MartinBraquet
6b3def230b Add liberapay donation link 2025-10-29 17:20:47 +01:00
MartinBraquet
51c46db106 Fix 2025-10-29 17:06:39 +01:00
MartinBraquet
64c18179ac Fix 2025-10-29 16:50:06 +01:00
MartinBraquet
5ee0b39e07 Hide settings when logged out 2025-10-29 16:29:14 +01:00
MartinBraquet
6470319fd6 Add settings page 2025-10-29 16:25:03 +01:00
MartinBraquet
4ca3f3c8ee Delete auth user right away 2025-10-29 16:24:49 +01:00
MartinBraquet
07e2d2d509 Fix red color 2025-10-29 16:21:58 +01:00
MartinBraquet
07d2a143a2 Create base settings page 2025-10-29 12:24:08 +01:00
MartinBraquet
968845492f Comment unused function 2025-10-29 12:23:53 +01:00
MartinBraquet
c2106b64f9 Add webview_oauth_signin.md 2025-10-29 12:08:20 +01:00
MartinBraquet
0d73d1d258 Delete 2025-10-28 18:43:39 +01:00
MartinBraquet
423d425950 Roolback 2025-10-28 18:42:37 +01:00
MartinBraquet
7ab0093fec Sign in failing for android 2025-10-28 18:41:54 +01:00
MartinBraquet
56d2757448 Add styles 2025-10-28 18:32:21 +01:00
MartinBraquet
f5b6037367 Move log 2025-10-28 18:02:45 +01:00
MartinBraquet
2c4ce6c8d1 Add isAndroidWebView 2025-10-28 17:33:25 +01:00
MartinBraquet
f9ccd3628a Add logs 2025-10-28 16:56:45 +01:00
MartinBraquet
abef2b394e Add aiexclude 2025-10-28 16:51:00 +01:00
MartinBraquet
97ff6f1de9 Add base android webview app 2025-10-28 16:05:22 +01:00
MartinBraquet
7fad4435cb Add android readme 2025-10-28 15:16:19 +01:00
MartinBraquet
92a97209ca Add yarn build-web 2025-10-28 15:16:13 +01:00
MartinBraquet
e7c3f083b4 Update NEXT_PUBLIC_LOCAL_ANDROID check 2025-10-28 15:16:02 +01:00
MartinBraquet
7b5961f941 Add log 2025-10-28 01:34:29 +01:00
MartinBraquet
85d4b411b5 Add log 2025-10-28 01:10:51 +01:00
MartinBraquet
c9ec32aca7 Fix APK discrimination 2025-10-28 00:37:02 +01:00
MartinBraquet
13a3013a8e Add @capgo/capacitor-social-login 2025-10-28 00:16:28 +01:00
MartinBraquet
0dd3bac855 Add googleNativeLogin 2025-10-28 00:10:44 +01:00
MartinBraquet
d7a716a5cb Add -H 0.0.0.0 2025-10-28 00:04:02 +01:00
MartinBraquet
0bbc9cbe81 Add IS_LOCAL_ANDROID 2025-10-28 00:03:54 +01:00
MartinBraquet
df766d8d1f Fix typo 2025-10-28 00:02:28 +01:00
MartinBraquet
20a150a228 Remove ; 2025-10-27 21:45:17 +01:00
MartinBraquet
010292a440 Remove cached dataa 2025-10-27 21:44:29 +01:00
MartinBraquet
394dae18e9 Move FCM inside 2025-10-27 21:44:19 +01:00
MartinBraquet
f1676c52f0 Add mobile push notifications 2025-10-27 16:12:51 +01:00
MartinBraquet
05f6f3c79b Install capacitor android 2025-10-27 13:13:52 +01:00
MartinBraquet
9942b488ea Serve mobile app from url 2025-10-27 13:13:41 +01:00
MartinBraquet
5d83f4bf2d Add build web script 2025-10-27 13:13:25 +01:00
MartinBraquet
d9afd914ff Make SEA description dynamic 2025-10-27 12:56:06 +01:00
MartinBraquet
990d8160f8 Use SSR for news 2025-10-27 12:49:08 +01:00
MartinBraquet
80c321b66f Remove log 2025-10-27 12:48:12 +01:00
MartinBraquet
67b45f3e5c Add capacitor 2025-10-27 11:49:39 +01:00
MartinBraquet
ca3cee5673 Move paragraph 2025-10-27 01:47:23 +01:00
MartinBraquet
ae0d170244 Improve wording 2025-10-27 01:46:10 +01:00
MartinBraquet
9a31cfa938 Fixes 2025-10-27 01:34:30 +01:00
MartinBraquet
cdbc9c305e Allow null for profiles 2025-10-27 01:23:20 +01:00
MartinBraquet
cdbce13c49 Sort zod types 2025-10-27 00:39:48 +01:00
MartinBraquet
0a41ebbcda Add an option to disable your profile 2025-10-27 00:37:54 +01:00
MartinBraquet
476fe1602b Clean 2025-10-27 00:37:18 +01:00
MartinBraquet
2f482e9afc Fix links 2025-10-26 18:50:14 +01:00
MartinBraquet
d59e6e0691 Fix news link (open in no tab) 2025-10-26 18:37:44 +01:00
MartinBraquet
7ec6866f26 Add news to FAQ 2025-10-26 16:37:16 +01:00
MartinBraquet
3686e7facf Remove changelog 2025-10-26 16:32:00 +01:00
MartinBraquet
1aba1894ea Remove unused react 2025-10-26 16:24:38 +01:00
MartinBraquet
14503c9b8f Fix header color 2025-10-26 16:24:12 +01:00
MartinBraquet
a315668d31 Fix format 2025-10-26 16:19:42 +01:00
MartinBraquet
db9ea63210 Add what's new page 2025-10-26 16:07:57 +01:00
MartinBraquet
51ecbd5b53 Add dark swagger docs 2025-10-26 15:45:47 +01:00
MartinBraquet
45ef0d9809 Add /donate to /support 2025-10-26 14:37:05 +01:00
MartinBraquet
356702b50d Hide local endpoints when deployed 2025-10-26 14:34:09 +01:00
MartinBraquet
e72ce5376c Remove unused pages 2025-10-26 14:24:48 +01:00
MartinBraquet
0d35f3fbd2 Add bottom margin 2025-10-26 14:24:17 +01:00
MartinBraquet
28c22c1eae Add qr code to FAQ 2025-10-26 14:24:04 +01:00
MartinBraquet
7cf83f65c3 Cache vote creators 2025-10-26 13:53:46 +01:00
MartinBraquet
4c4f2e720d Add status on proposals 2025-10-26 10:51:52 +01:00
MartinBraquet
0fa562e6fd Rename order by 2025-10-26 10:01:36 +01:00
MartinBraquet
bcd0f778cf Rename vote title 2025-10-26 10:01:20 +01:00
MartinBraquet
401ab9f706 Add bottom margin 2025-10-26 09:42:20 +01:00
MartinBraquet
8b09a81d5a Add keyword example 2025-10-25 23:54:50 +02:00
MartinBraquet
86718cc406 Sleep more more more 2025-10-25 23:20:17 +02:00
MartinBraquet
ccb72364e1 Sleep more 2025-10-25 23:08:55 +02:00
MartinBraquet
bfd6a59d87 Increase cache check 2025-10-25 23:08:48 +02:00
MartinBraquet
af4caa455a Fix typos 2025-10-25 23:00:54 +02:00
MartinBraquet
d511e4a75c Comment log 2025-10-25 23:00:46 +02:00
MartinBraquet
8fd906223c Re enable dev cache 2025-10-25 23:00:40 +02:00
MartinBraquet
deadb56aaa Try to load profiles times if not found 2025-10-25 23:00:25 +02:00
MartinBraquet
1ff867879c Throttle discord message 2025-10-25 23:00:02 +02:00
MartinBraquet
f3630dd868 Send generic error message instead of error log 2025-10-25 22:30:08 +02:00
MartinBraquet
39143525c3 Revalidates ISR pages 2025-10-25 22:18:48 +02:00
MartinBraquet
e8dd1f8f8b Add log 2025-10-25 21:48:25 +02:00
MartinBraquet
28e5d2e3f2 Fix profile not found due to cache 2025-10-25 21:40:29 +02:00
MartinBraquet
21def91427 Fix plus icon color 2025-10-25 21:22:29 +02:00
MartinBraquet
bc5d04c662 Set user_id?: string 2025-10-25 16:57:51 +02:00
MartinBraquet
c736227448 Add response to send test email 2025-10-25 16:52:11 +02:00
MartinBraquet
168285cb64 Add contact info to /contact 2025-10-25 16:37:15 +02:00
MartinBraquet
3411f50d29 Add /local/send-test-email endpoint 2025-10-25 16:33:30 +02:00
MartinBraquet
319c14b0e0 Add react import in email
Required since using tsx watch src/serve.ts and not nodemon
2025-10-25 16:32:55 +02:00
MartinBraquet
64c077396f Rename fromEmail 2025-10-25 16:32:07 +02:00
MartinBraquet
65f0d448a1 Fix messages not sent 2025-10-25 15:48:42 +02:00
MartinBraquet
2fdaa464dd Remove import 2025-10-25 15:33:23 +02:00
MartinBraquet
f86a6a10ac Add political_details 2025-10-25 15:32:16 +02:00
MartinBraquet
08a2438e79 Add Animist 2025-10-25 15:31:53 +02:00
MartinBraquet
60cc47f7ca Improve rendering of religion in about me 2025-10-25 14:45:53 +02:00
MartinBraquet
7e4f606492 Improve filter formatting for politics and religion 2025-10-25 14:36:55 +02:00
MartinBraquet
8ff58534d9 Update docs 2025-10-25 14:24:14 +02:00
MartinBraquet
a4bb184e95 Add filter: religion 2025-10-25 14:22:11 +02:00
MartinBraquet
940c1f5692 Add profile field: religion 2025-10-25 14:21:58 +02:00
MartinBraquet
0430733b58 Add internal endpoints to API docs 2025-10-25 13:44:00 +02:00
MartinBraquet
33136816af Improve API docs 2025-10-25 13:31:44 +02:00
MartinBraquet
469a235799 Remove logs 2025-10-25 04:40:11 +02:00
MartinBraquet
2d71c827b3 Fix 2025-10-25 04:30:21 +02:00
MartinBraquet
17f9e72a9f Release 2025-10-25 04:19:29 +02:00
MartinBraquet
120aeed56f Add API to FAQ 2025-10-25 04:19:19 +02:00
MartinBraquet
8128c3b2d7 Update docs 2025-10-25 04:10:37 +02:00
MartinBraquet
4581a33cae Fix package import 2025-10-25 04:10:32 +02:00
MartinBraquet
d43e2af3ae Remove unused openapi 2025-10-25 04:07:46 +02:00
MartinBraquet
0283eb4d85 Massive upgrade to the API Swagger UI @ api.compassmeet.com 2025-10-25 03:42:23 +02:00
MartinBraquet
f483ae42a8 Add smoker to your filters 2025-10-25 01:31:43 +02:00
MartinBraquet
f974eba465 Move Reset filter to top 2025-10-25 01:25:24 +02:00
MartinBraquet
7d7969fe0f Reset filter 2025-10-25 01:22:01 +02:00
MartinBraquet
2a3d7e8362 Fix you filter 2025-10-25 01:21:46 +02:00
MartinBraquet
a38c03c4e0 Fix 2025-10-25 01:02:21 +02:00
MartinBraquet
342a0c612a Add filter for smoking 2025-10-25 00:58:53 +02:00
MartinBraquet
f1f9970407 Either instead of Any Kids 2025-10-24 23:03:41 +02:00
MartinBraquet
c83a3e6315 Log error 2025-10-24 23:03:17 +02:00
MartinBraquet
fbc65e7e2a Update helpers for local run 2025-10-24 23:03:04 +02:00
MartinBraquet
d9e9407cab Update doc 2025-10-24 23:02:36 +02:00
MartinBraquet
d0881b76e0 Update migrate 2025-10-24 23:02:28 +02:00
MartinBraquet
61c867b49c Add info for add new profile field 2025-10-24 23:02:17 +02:00
MartinBraquet
87de30d257 Create share notif 2025-10-24 23:02:03 +02:00
MartinBraquet
817605417c Default to DEV in local 2025-10-24 23:01:50 +02:00
MartinBraquet
65b018db2a Add contact links 2025-10-24 20:48:45 +02:00
MartinBraquet
addb52e3fa Add /donate page with OC widget 2025-10-24 19:24:58 +02:00
MartinBraquet
c3124ec7c3 Clean 2025-10-24 18:52:00 +02:00
MartinBraquet
b1caa6dfdc Remove log 2025-10-24 18:34:55 +02:00
MartinBraquet
26f28d55d9 Add filters for drinks 2025-10-24 18:31:43 +02:00
MartinBraquet
cb66688529 Make left nav bar scrollable 2025-10-24 17:20:52 +02:00
MartinBraquet
40c61f11be Speed update local run 2025-10-24 17:10:47 +02:00
MartinBraquet
9b45c75a5b Remove none 2025-10-24 16:25:16 +02:00
MartinBraquet
09425c1910 Rename college 2025-10-24 16:24:01 +02:00
MartinBraquet
591798e98c Add filter for education level 2025-10-24 16:19:37 +02:00
MartinBraquet
acdd82a680 Clean and factor out get-private-messages 2025-10-24 15:18:04 +02:00
MartinBraquet
5719ac3209 Fix age input 2025-10-24 14:20:55 +02:00
MartinBraquet
2ac687b0c2 Fix 2025-10-24 02:41:10 +02:00
MartinBraquet
a86a249f05 Add height in cm 2025-10-24 02:36:30 +02:00
MartinBraquet
e49a7b0bb4 Add backend support for setting compatibility answers 2025-10-24 02:09:46 +02:00
MartinBraquet
e904a7949c Fix RLS 2025-10-24 01:57:01 +02:00
MartinBraquet
080d8110df Delete bookmarked searches in the backend 2025-10-24 01:35:16 +02:00
MartinBraquet
d90826e851 Create bookmarked searches in the backend 2025-10-24 01:30:03 +02:00
MartinBraquet
e495da692b Improve wording in saved searches 2025-10-24 01:29:32 +02:00
MartinBraquet
52970ef93e Add fkeys to schema 2025-10-24 01:26:50 +02:00
MartinBraquet
8f641d117a Cancel deletions automated by cascade delete 2025-10-24 01:08:39 +02:00
MartinBraquet
d164ebc7da Add foreign keys 2025-10-24 01:04:18 +02:00
MartinBraquet
632cc5810d Add icons 2025-10-23 20:15:21 +02:00
MartinBraquet
e565a6c77f Set last online upon user creation 2025-10-23 18:45:37 +02:00
MartinBraquet
c1fe700d7a Add lib clean 2025-10-23 18:18:01 +02:00
MartinBraquet
06ee267804 Remove log 2025-10-23 18:00:11 +02:00
MartinBraquet
aad722c723 Add chat messages AES encryption 2025-10-23 17:57:19 +02:00
MartinBraquet
aefc58b636 Make mobile filter modal scrollable 2025-10-23 15:52:18 +02:00
MartinBraquet
fdd96507b8 Add politics filter 2025-10-23 15:41:46 +02:00
MartinBraquet
2ad87a5ec5 Update SQL schema 2025-10-23 15:25:06 +02:00
MartinBraquet
b94cdba5af Add diet 2025-10-23 14:58:57 +02:00
MartinBraquet
725261335c Reorder about 2025-10-23 14:16:50 +02:00
MartinBraquet
5fb0051fc6 Update README.md for clarity and minor corrections 2025-10-23 02:25:47 +02:00
MartinBraquet
1247847739 Update FAQ 2025-10-23 02:25:34 +02:00
MartinBraquet
18cb4e74d6 Refactor notification process 2025-10-23 02:07:45 +02:00
MartinBraquet
e07cb7fca9 Show all notifs types 2025-10-23 01:46:57 +02:00
MartinBraquet
dc54ed46f8 Add notif for New Vote Page 2025-10-23 01:38:34 +02:00
MartinBraquet
0415d86d71 Replace DROP INDEX with IF NOT EXISTS 2025-10-23 00:34:34 +02:00
MartinBraquet
b8b95be5ce Add on delete cascade 2025-10-23 00:29:14 +02:00
MartinBraquet
46820f0986 Clean 2025-10-23 00:08:54 +02:00
MartinBraquet
dcc022ac7f Add open Collective 2025-10-23 00:05:27 +02:00
MartinBraquet
9142f0d633 Autofocus 2025-10-22 23:49:28 +02:00
MartinBraquet
181c72befe Format 2025-10-22 23:49:18 +02:00
MartinBraquet
99f3459978 Remove user followers in search-users 2025-10-22 23:33:25 +02:00
MartinBraquet
75fbc9679c Fix page props 2025-10-22 23:33:12 +02:00
MartinBraquet
700b7774b1 Margin 404 2025-10-22 23:32:37 +02:00
MartinBraquet
d9f0a9b1ca Fix "younger than 99" 2025-10-22 22:59:25 +02:00
MartinBraquet
70644ff26d Prevent editor focus on mobile devices 2025-10-22 16:22:50 +02:00
MartinBraquet
bbefcc3bc8 Add indexes to push_subscriptions 2025-10-22 15:33:55 +02:00
MartinBraquet
09767dbae3 Add WPA to faq 2025-10-22 15:33:42 +02:00
MartinBraquet
57eafa95ba Fire and forget other user notif 2025-10-22 15:00:41 +02:00
MartinBraquet
f4f28a411e Typo 2025-10-22 15:00:27 +02:00
MartinBraquet
f6059ef5c7 Focus on editor upon page loading 2025-10-22 14:47:07 +02:00
MartinBraquet
e3fa4efa95 Remove expired subscriptions 2025-10-22 14:28:59 +02:00
MartinBraquet
6884a91eb8 Ignore ico 2025-10-22 14:28:23 +02:00
MartinBraquet
71ba018a42 Remove misplace favicon 2025-10-22 13:57:24 +02:00
MartinBraquet
10f5232ac3 Clean 2025-10-22 13:54:56 +02:00
MartinBraquet
78d707484d Update badge 2 2025-10-22 13:54:47 +02:00
MartinBraquet
69db66fbbb Update badge 2025-10-22 13:32:53 +02:00
MartinBraquet
99691cd7ee Factor out vapid key 2025-10-22 13:23:14 +02:00
MartinBraquet
47cef359ca Put back icons 2025-10-22 03:57:57 +02:00
MartinBraquet
046105498f Hide icons 2025-10-22 03:53:07 +02:00
MartinBraquet
4d3ef5dd2a Log payload 2025-10-22 03:43:56 +02:00
MartinBraquet
8bcd5623bf Add service worker info 2025-10-22 03:43:46 +02:00
MartinBraquet
a29b4a3a8e Remove badge 2025-10-22 03:40:15 +02:00
MartinBraquet
dee0fb396b Remove cache 2025-10-22 03:39:35 +02:00
MartinBraquet
b5c707e07f Send notif for every single private message 2025-10-22 03:25:04 +02:00
MartinBraquet
8fe35bd1d7 Add VAPID secrets 2025-10-22 03:02:34 +02:00
MartinBraquet
6c864c35cd Implement subscriptions for mobile notifications 2025-10-22 02:52:41 +02:00
MartinBraquet
f00acf6af1 Improve link accessibility 2025-10-22 01:18:45 +02:00
MartinBraquet
49e1599bc4 Fix react error 2025-10-22 00:44:22 +02:00
MartinBraquet
7311d4b724 Remove cache 2025-10-22 00:38:56 +02:00
MartinBraquet
fa44e348a2 Fix WPA theme color 2025-10-22 00:22:28 +02:00
MartinBraquet
8cba02741c Fix WPA 2025-10-22 00:01:58 +02:00
MartinBraquet
48d04d5e72 Add WPA 2025-10-21 23:37:20 +02:00
MartinBraquet
7cac25c0e2 Remove react import (2) 2025-10-21 20:50:56 +02:00
MartinBraquet
88b0fa0163 Remove react import 2025-10-21 20:32:35 +02:00
MartinBraquet
3fcef24cc9 Add SEO (including better tab titles) 2025-10-21 20:29:10 +02:00
MartinBraquet
d9fba6ce6b Fix link 2025-10-21 12:40:28 +02:00
MartinBraquet
8bc2f0c40e Fix flashing submit button 2025-10-21 12:36:54 +02:00
MartinBraquet
21254695d5 Remove unused import 2025-10-21 12:32:39 +02:00
MartinBraquet
f063f0a6f4 Add donate links 2025-10-21 12:32:00 +02:00
MartinBraquet
2d847cbcdb Add Github sponsor 2025-10-21 12:15:27 +02:00
MartinBraquet
547e99f526 Add vercel config for backup info 2025-10-20 17:36:55 +02:00
MartinBraquet
a9794cd2ee Add packages 2025-10-20 17:24:42 +02:00
MartinBraquet
c651abd8ae Add unmet deps 2025-10-20 16:45:02 +02:00
MartinBraquet
15781475b6 Rename Love to Profile 2025-10-20 16:35:59 +02:00
MartinBraquet
26a28175fd Rename LovePage 2025-10-20 16:24:22 +02:00
MartinBraquet
aa3680934b Rename lover to profile 2025-10-20 16:18:49 +02:00
MartinBraquet
0b36586ddf Rename love folder in backend 2025-10-20 16:14:23 +02:00
MartinBraquet
7b58acac0d Rename love folder 2025-10-20 16:13:43 +02:00
MartinBraquet
27bf4eadf9 Rename compatibility_answers_free 2025-10-20 16:10:41 +02:00
MartinBraquet
c8d4353888 Rename love_waitlist 2025-10-20 16:05:14 +02:00
MartinBraquet
4876ca2643 Rename love_stars 2025-10-20 16:03:28 +02:00
MartinBraquet
e06a382c94 Rename love_ships 2025-10-20 16:01:33 +02:00
MartinBraquet
d1a421ca15 Rename compatibility_prompts 2025-10-20 15:59:44 +02:00
MartinBraquet
cd3c8d89d0 Rename love_likes 2025-10-20 15:48:22 +02:00
MartinBraquet
1f943ccead Rename love_compatibility_answers 2025-10-20 15:46:16 +02:00
MartinBraquet
753776fa9a Fix 2025-10-20 15:37:33 +02:00
MartinBraquet
9787a2446e Fix 2025-10-20 15:31:39 +02:00
MartinBraquet
4cb29d274b Add floating info box 2025-10-20 15:28:59 +02:00
MartinBraquet
df55d63f99 Add security and help pages 2025-10-20 13:31:58 +02:00
MartinBraquet
236e2d48c5 Update security email 2025-10-20 13:20:31 +02:00
MartinBraquet
30d45d834f Add compat prompts number to stats 2025-10-20 13:03:26 +02:00
MartinBraquet
edf30897f2 Fix effect 2025-10-20 12:56:42 +02:00
MartinBraquet
3d31ebb576 Fix hooks 2025-10-20 12:55:16 +02:00
MartinBraquet
d3bac8bcc0 Fix vercel badge 2025-10-20 12:53:37 +02:00
MartinBraquet
a360f80cdf Add warning message about short bio 2025-10-20 12:51:28 +02:00
MartinBraquet
0cc7549546 remove log 2025-10-20 12:51:10 +02:00
MartinBraquet
283d2743e0 Fix mod 2025-10-19 22:50:09 +02:00
MartinBraquet
b431fa11fa Fix pref_gender filtering 2025-10-19 21:42:37 +02:00
MartinBraquet
648e00867f Reduce v space 2025-10-19 21:30:44 +02:00
MartinBraquet
552af7bb6b Add romantic type in filters 2025-10-19 21:25:17 +02:00
MartinBraquet
92980f7c79 Increase contrast 2025-10-19 20:19:52 +02:00
MartinBraquet
09a563bf73 Clean 2025-10-19 20:19:45 +02:00
MartinBraquet
141fa12a20 Add kids strength and other relationship filters 2025-10-19 12:36:48 +02:00
MartinBraquet
6e0035d4f3 Show profiles that don't set kids strength 2025-10-19 12:36:09 +02:00
MartinBraquet
97bac4132c Fix loc filter clearing 2025-10-19 11:11:51 +02:00
MartinBraquet
b23b0280cd Fix mobile nav contrast 2025-10-19 11:00:49 +02:00
MartinBraquet
7ac093a8d0 Fix socials contrast 2025-10-19 10:58:12 +02:00
MartinBraquet
dfc524b957 Fix 2025-10-19 00:32:48 +02:00
MartinBraquet
65ba0d348b Increase contrast for better accessibility 2025-10-19 00:11:59 +02:00
MartinBraquet
ed07031539 Fix primary key 2025-10-18 23:31:32 +02:00
MartinBraquet
93f3690344 Speed up init theme 2025-10-18 22:47:43 +02:00
MartinBraquet
1341d1356a Fix warning 2025-10-18 22:37:46 +02:00
MartinBraquet
38dcf16c03 customlink -> custom-link 2025-10-18 22:36:37 +02:00
MartinBraquet
8696a42959 SSH and view logs in one click 2025-10-18 22:36:12 +02:00
MartinBraquet
c6fc7db1e9 Move up 2025-10-18 13:17:44 +02:00
MartinBraquet
58540aca57 Add proposals and votes number to stats 2025-10-18 13:16:05 +02:00
MartinBraquet
b7b75279c2 Add warning message upon console opening 2025-10-18 12:49:36 +02:00
MartinBraquet
204a35d026 Release 1.4 2025-10-18 12:24:13 +02:00
MartinBraquet
fb2841f198 Update compat answer box 2025-10-18 12:23:47 +02:00
MartinBraquet
5de055c977 Improve importance radio contrast 2025-10-18 12:14:35 +02:00
MartinBraquet
084659ea3d Remove debug 2025-10-18 12:14:20 +02:00
MartinBraquet
c1a414afab Make votes sortable 2025-10-18 11:49:54 +02:00
MartinBraquet
a5747034d6 Fix props name 2025-10-18 10:40:35 +02:00
MartinBraquet
fda52fec97 Move proposal up and hide by default 2025-10-18 10:39:50 +02:00
MartinBraquet
e38ec79618 remove quote 2025-10-18 02:50:05 +02:00
MartinBraquet
1ef125db12 Fix md format 2025-10-18 02:46:07 +02:00
MartinBraquet
b580b640bd Remove unused react 2025-10-18 02:39:24 +02:00
MartinBraquet
214bddaca4 Add contact links 2025-10-18 02:36:54 +02:00
MartinBraquet
065d489869 Add contact form 2025-10-18 02:20:31 +02:00
MartinBraquet
46ffefbbb9 Add anonymous option for votes 2025-10-18 00:53:35 +02:00
MartinBraquet
a19db3bca9 Clean 2025-10-18 00:20:32 +02:00
MartinBraquet
2c8d8d9989 Clean 2025-10-18 00:12:53 +02:00
MartinBraquet
d52943e31e Fix 2025-10-17 23:24:08 +02:00
MartinBraquet
3eababb742 Fix 2025-10-17 23:19:20 +02:00
MartinBraquet
8a954d3c20 Add voting / proposal page 2025-10-17 23:15:15 +02:00
MartinBraquet
8516901032 Allow get notified for anyone 2025-10-17 19:04:57 +02:00
MartinBraquet
3f2d246fec Fix short bios not showing when sorting by compatibility 2025-10-17 16:55:44 +02:00
MartinBraquet
58fdaa26ca Move to distance filtering to improve accuracy and speed 2025-10-17 16:43:27 +02:00
MartinBraquet
7dc1a8790d Fix 2025-10-17 14:53:55 +02:00
MartinBraquet
70c9ec1d73 Show full political names 2025-10-17 14:02:04 +02:00
MartinBraquet
2bcbbc96ad Add political options 2025-10-17 13:55:48 +02:00
MartinBraquet
527d36a159 Move want kids closer to connection type 2025-10-17 13:50:46 +02:00
MartinBraquet
2ce21247ee Add romantic style (poly, mono, other) 2025-10-17 13:42:32 +02:00
MartinBraquet
8ea6c406e0 Add webhook to report to discord 2025-10-16 20:59:46 +02:00
MartinBraquet
e22f50ecd3 Show loading indicator 2025-10-16 15:28:29 +02:00
MartinBraquet
20dcd98fdf Allow unauth requests to get-messages-count (used in public stats) 2025-10-16 15:16:21 +02:00
MartinBraquet
bc5708857a Improve onboarding UI 2025-10-16 14:37:17 +02:00
MartinBraquet
b9c045ebfb Do not render sign up button, redirect to home 2025-10-16 14:22:53 +02:00
MartinBraquet
c69bd7018e Use compass loading sign 2025-10-16 14:08:34 +02:00
MartinBraquet
078d149175 Redirect to profiles grid after sign up 2025-10-16 14:08:24 +02:00
MartinBraquet
be9f0bd061 Add 20 core compatibility prompts 2025-10-16 13:48:00 +02:00
MartinBraquet
a4723563f5 Keep blue loading circle for buttons 2025-10-16 13:41:03 +02:00
MartinBraquet
1fdcd24f28 Improve design of loading indicator 2025-10-16 12:42:38 +02:00
MartinBraquet
a43480db92 Increase API_RATE_LIMIT_PER_MIN_UNAUTHED 2025-10-16 01:27:24 +02:00
MartinBraquet
e85a072f1c Add user loaded log 2025-10-16 01:24:05 +02:00
MartinBraquet
bbfa2a4eab Wait longer for user to appear 2025-10-16 01:23:12 +02:00
MartinBraquet
2f2db4ded8 Rollback toast error as it shows randomly 2025-10-16 00:48:38 +02:00
MartinBraquet
7296a0d2cd Remove rate limit for endpoints not prone to scraping 2025-10-16 00:41:21 +02:00
MartinBraquet
08e02b6ac0 Add too many requests toast 2025-10-16 00:28:25 +02:00
MartinBraquet
715811d7fd Commetn 2025-10-16 00:28:10 +02:00
MartinBraquet
c7d6ae6995 Hide log 2025-10-16 00:27:58 +02:00
MartinBraquet
b1d1396944 Fix import 2025-10-15 23:52:46 +02:00
MartinBraquet
25a319710e Fix import 2025-10-15 23:50:27 +02:00
MartinBraquet
796b13dd62 Add toast error for too many requests 2025-10-15 23:47:01 +02:00
MartinBraquet
8197863ac5 Clean auth and rate limiting 2025-10-15 23:37:24 +02:00
MartinBraquet
89bd164d43 Add authed 2025-10-15 23:20:20 +02:00
MartinBraquet
80d7061e5f Pre commit 2025-10-15 22:50:50 +02:00
MartinBraquet
c49bac3a09 Make API calls authed 2025-10-15 22:42:26 +02:00
MartinBraquet
06d53fe801 Redirect if logged out in /notifications 2025-10-15 22:32:58 +02:00
MartinBraquet
15ba529938 Fix "column reference "user_id" is ambiguous" 2025-10-15 19:26:10 +02:00
MartinBraquet
83054d0cd1 Fix link opening in same tab 2025-10-15 17:04:00 +02:00
MartinBraquet
8da486adf2 Optimistically remove starred profile upon deletion 2025-10-15 16:01:11 +02:00
MartinBraquet
32bc3847fa Add option to save / bookmark profiles 2025-10-15 15:45:47 +02:00
MartinBraquet
5d763c18c8 Comment log 2025-10-15 15:45:27 +02:00
MartinBraquet
bd3920cfff Fix pagination for last active sorting 2025-10-14 22:09:20 +02:00
MartinBraquet
06d94332b6 Update keyword search placeholder names 2025-10-14 21:35:37 +02:00
MartinBraquet
50614484d8 Move last above social links 2025-10-14 21:06:08 +02:00
MartinBraquet
c29d3d8c92 Clean 2025-10-14 20:51:06 +02:00
MartinBraquet
26f46af375 Fix unused botBadge 2025-10-14 20:49:36 +02:00
MartinBraquet
32b1491dd0 Fix unused node 2025-10-14 20:48:50 +02:00
MartinBraquet
51b8a6c80a Fix unused open 2025-10-14 20:48:21 +02:00
MartinBraquet
0f63d6d3a0 Remove unused react imports 2025-10-14 20:42:41 +02:00
MartinBraquet
4771b08773 Show and write when the user was last online in their profile 2025-10-14 20:36:58 +02:00
MartinBraquet
9b880101fd Make plot larger on mobile 2025-10-14 19:56:35 +02:00
MartinBraquet
594806d6e8 Improve charts design 2025-10-14 19:37:38 +02:00
MartinBraquet
e9afd4db2f Regen supabase types 2025-10-14 19:11:26 +02:00
MartinBraquet
b23efe4089 Add active members tile and move /charts to /stats 2025-10-14 19:09:56 +02:00
MartinBraquet
e33be41a93 Store last_online_time in user_activity.sql table instead of profiles 2025-10-14 19:09:08 +02:00
MartinBraquet
33b09df872 Reduce chart height 2025-10-14 19:03:30 +02:00
MartinBraquet
e9050d0aa0 Simplify and make grid for organization.tsx 2025-10-14 18:57:59 +02:00
MartinBraquet
baeb2a33fe Simplify and make grid for social 2025-10-14 18:57:52 +02:00
MartinBraquet
4ad89acdc7 Use last_online_time from user_activity instead of profiles 2025-10-14 17:53:29 +02:00
MartinBraquet
7d87af8f5c Add user_activity.sql 2025-10-14 17:52:09 +02:00
MartinBraquet
65c0e84e2a Do not prepend social url if full url is provided 2025-10-14 11:28:48 +02:00
MartinBraquet
7b15d85871 Do not render protocol and subdomain in socials 2025-10-14 11:28:21 +02:00
MartinBraquet
ad8ec0f4fd Add browser dev info 2025-10-14 11:27:39 +02:00
MartinBraquet
2d05d83dd0 Always show relationship questions when connection type includes relationships 2025-10-14 11:02:50 +02:00
MartinBraquet
bd45066b13 Add IDE note 2025-10-13 19:43:06 +02:00
MartinBraquet
8ee4274054 Rename voting members 2025-10-13 19:13:13 +02:00
MartinBraquet
83a7ed4d6b Add stats to organization page 2025-10-13 19:05:41 +02:00
MartinBraquet
07dbd86ac6 Add How fast is Compass growing? to FAQ 2025-10-13 18:49:05 +02:00
MartinBraquet
0e671d2cc0 Add nice stats 2025-10-13 18:42:39 +02:00
MartinBraquet
2d6d3c04ce Add stat box 2025-10-13 18:42:06 +02:00
MartinBraquet
b0148963c7 Remove bookmarked_searches and love_compatibility_answers upon account deletion 2025-10-13 17:35:12 +02:00
MartinBraquet
13356950f3 Wait instead of thread resend emails 2025-10-13 17:27:59 +02:00
MartinBraquet
629bcb30a7 Add health discord webhook and send error message there 2025-10-13 15:13:05 +02:00
MartinBraquet
03721fff1c Do not pass orderBy when processing saved searches 2025-10-13 15:12:24 +02:00
MartinBraquet
2a6911ae3d Move email links to our domain 2025-10-13 13:34:10 +02:00
MartinBraquet
164eddecab Release v1.3.0 2025-10-13 12:39:38 +02:00
MartinBraquet
9eacb38eb9 Show bio in discord message of profile creation 2025-10-13 12:26:40 +02:00
MartinBraquet
20f5cfb9a7 Fix demo 2025-10-13 10:49:19 +02:00
Martin Braquet
6c6c1cc90a Upgrade geodb plan to increase radius and page limit (#14)
* Upgrade geodb plan to increase radius and page limit

* Speed location debounce
2025-10-12 15:58:52 +02:00
MartinBraquet
a32c099cc1 Rename social 2025-10-12 14:54:58 +02:00
MartinBraquet
fe2f832e83 Improve support page 2025-10-12 14:52:39 +02:00
MartinBraquet
868746cc23 Use Atkinson Hyperlegible font 2025-10-12 14:35:59 +02:00
MartinBraquet
3be7a54284 Fix compat modal (had to scroll to see Next on mobile) 2025-10-12 13:13:01 +02:00
MartinBraquet
635e1ec8e2 Add TODO readme info 2025-10-11 23:23:52 +02:00
MartinBraquet
a638a35a76 Upgrade ban logic 2025-10-11 22:58:50 +02:00
MartinBraquet
8cc33d3418 Add massive upgrade text 2025-10-11 21:42:46 +02:00
MartinBraquet
9947f7b967 Fix 2025-10-11 21:42:33 +02:00
MartinBraquet
daf5350f41 Add stem vector search in bio 2025-10-11 21:15:03 +02:00
MartinBraquet
020b9ddb8d Fix 2025-10-11 19:56:54 +02:00
MartinBraquet
23aff9497a Fix 2025-10-11 19:54:25 +02:00
MartinBraquet
3c119396f3 Add demo 2025-10-11 19:51:48 +02:00
MartinBraquet
f7c7c47ac0 Remove backup info from git 2025-10-11 19:44:38 +02:00
MartinBraquet
dbe2369bbe Fix avatar link 2025-10-11 19:44:12 +02:00
MartinBraquet
4e8033d221 Add info about contact 2025-10-11 19:44:02 +02:00
MartinBraquet
97a0f87cbd Use georgia font 2025-10-11 12:15:26 +02:00
MartinBraquet
bfa2713d43 Fix wording 2025-10-11 11:46:38 +02:00
MartinBraquet
fe5e109751 Improve reading 2025-10-10 22:53:30 +02:00
MartinBraquet
8cc96030b1 Speed up placeholder 2025-10-10 22:52:09 +02:00
MartinBraquet
a2b172ad58 Improve charts 2025-10-10 21:31:30 +02:00
MartinBraquet
e756225d8b Move pics above endorsements 2025-10-10 20:58:06 +02:00
MartinBraquet
dd803b604f Update reserved paths 2025-10-10 20:20:10 +02:00
MartinBraquet
b5c961c8ee Hide complete profile button 2025-10-10 20:05:01 +02:00
MartinBraquet
47cd9d227e Add shortBio filter to mobile filters 2025-10-10 19:13:32 +02:00
MartinBraquet
e2be3aafcd Add shortBio filter 2025-10-10 19:03:57 +02:00
MartinBraquet
015fe76c44 Hide profiles with small bio 2025-10-10 18:33:47 +02:00
MartinBraquet
44666aec03 Update post install 2025-10-10 18:33:11 +02:00
MartinBraquet
6a265e4f35 Do not render home before user loads 2025-10-10 18:32:37 +02:00
MartinBraquet
12c7316524 Refactor buttons 2025-10-10 17:04:26 +02:00
MartinBraquet
dcf9741d69 Format required form as step by step onboarding 2025-10-10 16:46:17 +02:00
MartinBraquet
63dd1fdd50 Replace user with voting member and member with volunteer for clarity and inclusion 2025-10-10 15:22:55 +02:00
MartinBraquet
5aa166bbfd Open links in same tab 2025-10-10 15:12:48 +02:00
MartinBraquet
34cbf7093e Skip welcome email if local 2025-10-10 14:51:22 +02:00
MartinBraquet
159d58949e Reformat 2025-10-09 21:51:21 +02:00
MartinBraquet
fcf802b7e3 Refactor bios and add character counter 2025-10-09 21:51:08 +02:00
MartinBraquet
92ff6dadb0 Add email 2025-10-09 20:00:01 +02:00
MartinBraquet
05fa2f9883 Add socials and organization pages 2025-10-09 19:47:32 +02:00
MartinBraquet
71bb8fd784 Commetn 2025-10-09 19:33:51 +02:00
MartinBraquet
16ffd6dfab Fix message view without sign in 2025-10-09 19:30:24 +02:00
MartinBraquet
2661d15910 Remove waitlist 2025-10-09 19:17:15 +02:00
MartinBraquet
394102bb93 Fix avatar icon 2025-10-09 18:37:11 +02:00
MartinBraquet
3585b12dfd Remove maintenance banner 2025-10-09 18:20:53 +02:00
MartinBraquet
423d87d5f1 Remove logs 2025-10-09 18:19:14 +02:00
MartinBraquet
13b13b1104 Fix 2025-10-09 18:02:15 +02:00
MartinBraquet
a77e7b96b7 Move logs to debug status 2025-10-09 17:59:10 +02:00
MartinBraquet
d7213c255c Add client side heartbeat 2025-10-09 17:50:43 +02:00
MartinBraquet
ddeb1dcdb7 Improve ping pong connection duration 2025-10-09 17:38:05 +02:00
MartinBraquet
221cfa3528 Fix websockets not reaching the container and remove v0/ prefix 2025-10-09 16:58:20 +02:00
MartinBraquet
d6f6348ff1 Add maintenance banner 2025-10-09 16:25:47 +02:00
MartinBraquet
0c6afdc98e Add star 2025-10-09 15:14:38 +02:00
MartinBraquet
02a2148b3f Improve messages width 2025-10-09 13:14:38 +02:00
MartinBraquet
36a02268d8 Fix 2025-10-09 11:28:11 +02:00
MartinBraquet
450f07f505 Add private backup 2025-10-09 11:27:24 +02:00
MartinBraquet
777eba9fed Move backup to private storage 2025-10-09 11:23:30 +02:00
MartinBraquet
eaa8fa57d1 Add private bucket 2025-10-09 11:18:37 +02:00
MartinBraquet
200bf479e1 Clean 2025-10-09 00:43:27 +02:00
MartinBraquet
331f409af9 Increase debounce 2025-10-09 00:28:27 +02:00
MartinBraquet
ce875a5e63 Fix Porto not showing 2025-10-09 00:16:58 +02:00
MartinBraquet
638013f835 Update email address 2025-10-08 23:44:37 +02:00
MartinBraquet
1de87cbfec Add welcome email 2025-10-08 23:40:53 +02:00
MartinBraquet
7f3428b36a Factor out unsubscribe url 2025-10-08 23:40:37 +02:00
MartinBraquet
35595ded47 Fix bullets 2025-10-08 23:40:18 +02:00
MartinBraquet
35e9264017 Show profiles number, not users number 2025-10-08 23:39:42 +02:00
MartinBraquet
02d33c8f83 Rename mock user 2025-10-08 20:38:31 +02:00
MartinBraquet
f229ebc3a8 Add email confirmation 2025-10-08 20:38:20 +02:00
MartinBraquet
0062351f6d Add welcome email 2025-10-08 20:38:09 +02:00
MartinBraquet
e86f6798ec Fix bullet 2025-10-08 20:37:35 +02:00
MartinBraquet
4f53f7136b Add members 2025-10-08 20:35:57 +02:00
MartinBraquet
d80b982dde Simplify tab title 2025-10-08 17:32:19 +02:00
MartinBraquet
24788aa9af Add optional Garamond font 2025-10-08 14:11:51 +02:00
MartinBraquet
9ffae658df Clean 2025-10-08 11:58:58 +02:00
MartinBraquet
82ad573cac Add stoat link 2025-10-08 11:58:52 +02:00
MartinBraquet
36bf7ad65b Fix 2025-10-07 22:53:37 +02:00
MartinBraquet
b30af128c7 Release 2025-10-07 22:11:34 +02:00
MartinBraquet
72c31ae097 Add compat score math video link 2025-10-07 21:25:08 +02:00
Martin Braquet
d2c608021d Improve home (#10) 2025-10-05 10:38:04 +02:00
Martin Braquet
1f36fb2413 Update faq.md 2025-10-05 10:14:09 +02:00
Martin Braquet
16a0cbcecf Update about.tsx 2025-10-05 10:00:30 +02:00
Martin Braquet
e068e246aa Update faq.md 2025-10-03 13:58:31 +02:00
MartinBraquet
ec7c77fcf9 Log 2025-10-02 15:14:13 +02:00
MartinBraquet
46a338b874 Clean 2025-10-02 14:54:47 +02:00
MartinBraquet
bfee7ff09d Fix age rendering 2025-10-02 14:14:24 +02:00
MartinBraquet
ce1305d8ae Host logos 2025-10-02 13:17:22 +02:00
MartinBraquet
aaebf88438 Log profile count 2025-10-01 09:02:50 +02:00
MartinBraquet
dde2c99e36 Fix 2025-10-01 08:58:11 +02:00
MartinBraquet
4dc2f3b9b9 Add Community Growth over Time 2025-10-01 08:56:03 +02:00
MartinBraquet
f30cfffb86 Fix bio parsing grid 2025-09-30 22:17:28 +02:00
MartinBraquet
ca3eb62ba7 Fix 2 2025-09-30 21:48:15 +02:00
MartinBraquet
c8e55ca4ce Fix 2025-09-30 21:46:31 +02:00
MartinBraquet
e4acb25a40 Better render headings and lists in profile grid 2025-09-30 21:45:09 +02:00
MartinBraquet
c741e10139 Stop spamming prod discord channels 2025-09-30 21:30:38 +02:00
MartinBraquet
28d0b35f8e Move discord move to create profile 2025-09-30 20:53:26 +02:00
MartinBraquet
f7f09cd9e5 Send message to Discord when reaching 50, 100, ..., users 2025-09-28 21:09:11 +02:00
MartinBraquet
501c92c350 Send discord message at every profile creation 2025-09-28 20:47:31 +02:00
MartinBraquet
f021101322 Remove unused React 2025-09-26 22:17:24 +02:00
MartinBraquet
369265bc2c Add firebase storage backup script 2025-09-26 22:12:08 +02:00
Martin Braquet
b1f1e5db1f Fully delete profile in database and Firebase auth + storage (#7) 2025-09-26 22:10:38 +02:00
MartinBraquet
51d32e5afb Link donations in FAQ 2025-09-26 13:52:40 +02:00
MartinBraquet
f396e8e482 Your filters 2025-09-26 13:51:08 +02:00
MartinBraquet
077321731e Add bio warning message 2025-09-25 23:02:04 +02:00
MartinBraquet
60eb0c6978 Add 'datingdoc', 'friendshipdoc', 'connectiondoc', 'workdoc' 2025-09-25 22:49:54 +02:00
MartinBraquet
475f0af78a Add supabase backup VM to Firebase storage and discord error hook 2025-09-24 15:59:46 +02:00
MartinBraquet
206fa07035 Ignore tf 2025-09-24 15:13:33 +02:00
MartinBraquet
aff949714c Add charts example 2025-09-24 13:28:46 +02:00
Martin Braquet
7e834b9ff6 Update FUNDING.yml 2025-09-24 12:46:20 +02:00
Martin Braquet
19bad26a98 Create FUNDING.yml 2025-09-24 12:44:37 +02:00
MartinBraquet
7cc7c8d27b Hide age, city and gender if null 2025-09-22 11:06:52 +02:00
MartinBraquet
ae5a8c7cfa Add page loading warning 2025-09-22 11:02:53 +02:00
MartinBraquet
5004b73210 Fix calendly link 2025-09-22 10:52:46 +02:00
MartinBraquet
02f613d269 Add Ko-fi donation link 2025-09-22 10:42:23 +02:00
MartinBraquet
439ac0310b Add option to delete an answered compatibility prompt 2025-09-22 10:09:13 +02:00
MartinBraquet
3e95467819 Add okcupid and calendly links 2025-09-22 00:06:05 +02:00
MartinBraquet
90522cb88b Comment log 2025-09-22 00:05:15 +02:00
MartinBraquet
af39b01d4a Reload env config after setting env vars 2025-09-21 16:56:42 +02:00
MartinBraquet
73a0a5ff0b Fix vercel env 2025-09-21 16:08:29 +02:00
MartinBraquet
e157f500bc Fix dev firebase admin key 2025-09-21 16:00:29 +02:00
MartinBraquet
274ee5ed5f Remove json 2025-09-21 15:44:33 +02:00
MartinBraquet
4cb11ba8c0 Remove log 2025-09-21 15:00:26 +02:00
MartinBraquet
7b8e775139 Fix google cloud env 2025-09-21 14:55:32 +02:00
MartinBraquet
86a7d26bfd Clean readme 2025-09-20 23:54:56 +02:00
MartinBraquet
84a437772d Make local DEV work out of the box 2025-09-20 23:51:28 +02:00
MartinBraquet
d7c95e2ae0 Clean ENV 2025-09-20 18:26:03 +02:00
MartinBraquet
b4f0ef8b43 Move supabase dev pwd inside code 2025-09-20 18:12:46 +02:00
MartinBraquet
6d30cd7ae4 Add open source to FAQ 2025-09-19 14:47:58 +02:00
MartinBraquet
f631236ee7 Add platform to FAQ 2025-09-19 14:42:36 +02:00
MartinBraquet
1a58ff5c4c Shuffle prompts 2025-09-18 15:33:23 +02:00
MartinBraquet
73aca913a1 Fix 2025-09-18 14:14:58 +02:00
MartinBraquet
24dee0cad6 Add bio char template 2025-09-18 13:34:29 +02:00
MartinBraquet
2d2de75372 Add bio tips 2025-09-18 13:12:48 +02:00
MartinBraquet
d98982e6fd Factor out links 2025-09-18 11:30:59 +02:00
MartinBraquet
14c12ffb08 Rename 2025-09-18 11:19:09 +02:00
MartinBraquet
f260afca11 Ignore 2025-09-18 11:18:22 +02:00
MartinBraquet
5bcbe25d97 Rm 2025-09-18 11:18:04 +02:00
MartinBraquet
2eee366fbd Update financials 2025-09-18 11:12:33 +02:00
MartinBraquet
85d57ec5e6 Fix resend email limit 2/sec 2025-09-17 18:35:29 +02:00
MartinBraquet
502c878f82 Fix 2025-09-17 18:15:02 +02:00
MartinBraquet
1136c3f767 Release 2025-09-17 17:58:59 +02:00
MartinBraquet
42b496cd77 Fix 2025-09-17 17:58:11 +02:00
MartinBraquet
4acb5ee020 Fix warnings 2025-09-17 15:56:17 +02:00
MartinBraquet
ea18781cc6 Rename lover -> profile 2025-09-17 15:51:19 +02:00
MartinBraquet
593617c0ff Rename Lover -> Profile 2025-09-17 15:43:19 +02:00
MartinBraquet
c6a139d88d Rename Lovers -> Profiles 2025-09-17 15:42:23 +02:00
MartinBraquet
b7357a4546 Set 3 words 2025-09-17 15:25:27 +02:00
MartinBraquet
5eac959d15 FIx paypal path 2025-09-17 15:20:49 +02:00
MartinBraquet
74c86ecfbe FIx path 2025-09-17 15:19:17 +02:00
MartinBraquet
f353e590e1 Rename lovers -> profiles 2025-09-17 15:11:53 +02:00
MartinBraquet
a4cc3e10c2 Upgrade keywords examples 2025-09-17 12:00:24 +02:00
MartinBraquet
7321f56ee2 Upgrade docs 2025-09-16 23:49:17 +02:00
MartinBraquet
8800d9adc6 Upgrade README.md 2025-09-16 23:37:45 +02:00
MartinBraquet
22cd535527 Upgrade README.md 2025-09-16 23:35:02 +02:00
MartinBraquet
1d0e9592df Replace github sponsors 2025-09-16 22:53:53 +02:00
MartinBraquet
2ef4af0ff2 Fix links submit your own 2025-09-16 22:49:53 +02:00
MartinBraquet
542a6b1592 Fix endpoint 2025-09-16 22:42:37 +02:00
MartinBraquet
613ef94dba Upgrade docs endpoint 2025-09-16 22:28:40 +02:00
MartinBraquet
1dc2a1fadf Fix 2025-09-16 22:12:19 +02:00
MartinBraquet
41a606f5c1 Fix ages in filters 2025-09-16 22:09:43 +02:00
MartinBraquet
7b2b9855f9 Fix notifs page 2025-09-16 21:54:29 +02:00
MartinBraquet
b2b519ba2e Clean 2025-09-16 21:38:52 +02:00
MartinBraquet
5cf89392ff Fix username not being updated when loading their profile after registration 2025-09-16 21:38:19 +02:00
MartinBraquet
0f05304ec3 Fix typo 2025-09-16 21:07:02 +02:00
MartinBraquet
87bc962c88 Fix Rename github org 2025-09-16 18:59:21 +02:00
MartinBraquet
546ce6e229 Revert "Rename github org"
This reverts commit 2163d5aaf6.
2025-09-16 18:58:29 +02:00
MartinBraquet
2163d5aaf6 Rename github org 2025-09-16 18:49:36 +02:00
MartinBraquet
905ea160f2 Fix link 2025-09-16 18:42:48 +02:00
MartinBraquet
675f4a372b Update paypal links 2025-09-16 18:41:32 +02:00
MartinBraquet
7ff42db0c6 Curl API 2025-09-16 18:41:25 +02:00
MartinBraquet
a01283a446 Fix links 2025-09-16 18:18:57 +02:00
MartinBraquet
fefa261e7d Restrict matched users to last 24h 2025-09-16 18:09:48 +02:00
MartinBraquet
0447b22dd2 Restrict internal/send-search-notifications with API key 2025-09-16 17:54:13 +02:00
MartinBraquet
cf125c1b48 Fix packages 2025-09-16 16:48:52 +02:00
MartinBraquet
81a9d8257c Trigger filter change only on slide commit to avoid API overload 2025-09-16 16:15:07 +02:00
MartinBraquet
ee3f471300 Skip 2025-09-16 16:14:39 +02:00
MartinBraquet
5c2e5f626d Upgrade subject 2025-09-16 16:14:34 +02:00
MartinBraquet
a0f4b62361 Allow for empty orderBy 2025-09-16 16:14:26 +02:00
MartinBraquet
786166b448 Fix connection clause 2025-09-16 16:14:16 +02:00
MartinBraquet
66e198b4ef Ignore 2025-09-16 15:13:40 +02:00
MartinBraquet
4919240242 Update readme info 2025-09-16 15:13:36 +02:00
MartinBraquet
d7e6a41e3f Clean installs 2025-09-16 14:31:39 +02:00
MartinBraquet
202ef737dd Add react email as dev 2025-09-16 14:08:36 +02:00
MartinBraquet
04993224dc Nice emails 2025-09-16 14:03:08 +02:00
MartinBraquet
bebe7c28f8 Fix 2025-09-16 12:48:44 +02:00
MartinBraquet
639991dde4 Add bookmarked search emails and factor out utils from web to common 2025-09-16 12:36:18 +02:00
MartinBraquet
31404cb89a Add allowSyntheticDefaultImports 2025-09-16 12:35:19 +02:00
MartinBraquet
f6205ca1dd Add source.sh 2025-09-16 12:35:12 +02:00
MartinBraquet
6e86fc0593 Add build_api.sh 2025-09-16 12:35:01 +02:00
MartinBraquet
f39a9845a3 Fix tsconfig include jsonapi 2025-09-15 21:08:27 +02:00
MartinBraquet
ba17582945 Factor out loadProfiles 2025-09-15 21:08:14 +02:00
MartinBraquet
02a1cbd467 Rename getLovers and add base send-search-notifications.ts 2025-09-15 18:48:34 +02:00
MartinBraquet
2cd102ef0b Add API docs 2025-09-15 18:45:03 +02:00
MartinBraquet
240361b55b Access API at / install of /v0/ 2025-09-15 18:07:25 +02:00
MartinBraquet
9beabc93cd Clean 2025-09-15 16:13:11 +02:00
MartinBraquet
8f4c6b911a Meke age optional in API requests 2025-09-15 16:04:30 +02:00
MartinBraquet
083ef3010d Add location column 2025-09-15 14:24:47 +02:00
MartinBraquet
e6c2253219 Fix location pretty print 2025-09-14 22:45:04 +02:00
MartinBraquet
d802eb3f28 Fix 2025-09-14 22:36:16 +02:00
MartinBraquet
a342d5d5ad Fix hidden close button 2025-09-14 22:35:00 +02:00
MartinBraquet
99adb77fcb Pretty print bookmarked searches 2025-09-14 22:18:21 +02:00
MartinBraquet
2ea4eae9d6 Add wantsKidsNames 2025-09-14 22:16:45 +02:00
MartinBraquet
9b079b2c3a Add hasKidsNames 2025-09-14 22:16:30 +02:00
MartinBraquet
8648e8569e Add list style 2025-09-14 22:16:03 +02:00
MartinBraquet
1be0ab8bcb Fix 2025-09-14 16:37:57 +02:00
MartinBraquet
718f76c1f2 Hide likes 2025-09-14 16:37:52 +02:00
MartinBraquet
155d1f4c06 Add bookmarked filters for notifications 2025-09-13 23:21:45 +02:00
MartinBraquet
cb79e27d5a Fix hover button gray 2025-09-13 23:19:51 +02:00
MartinBraquet
26991f8dd8 Remove blue message 2025-09-13 23:19:29 +02:00
MartinBraquet
2375330d76 Fix modal size on mobile 2025-09-13 23:18:56 +02:00
MartinBraquet
94e9b6d99b Fix UI 2025-09-13 23:18:33 +02:00
MartinBraquet
b516d24101 Fix font 2025-09-13 23:18:11 +02:00
MartinBraquet
1b131d9371 Fix typo 2025-09-13 23:18:01 +02:00
MartinBraquet
3f45ef192d Update db schema 2025-09-13 23:17:46 +02:00
MartinBraquet
c6684af521 Improve colors 2025-09-13 23:17:23 +02:00
MartinBraquet
52f12b81ff Move script 2025-09-13 23:17:10 +02:00
MartinBraquet
6630f787bf Debug log 2025-09-13 23:16:58 +02:00
MartinBraquet
2d7b2da3e2 Improve wording 2025-09-13 23:16:53 +02:00
MartinBraquet
d3b008fcd9 Add bookmarked_searches.sql 2025-09-13 18:12:20 +02:00
MartinBraquet
8a62fd0e6a Add migration 2025-09-13 18:11:57 +02:00
MartinBraquet
b044860f05 Add last_modification_time 2025-09-13 18:11:46 +02:00
MartinBraquet
1c5786dfb6 Add web readme 2025-09-13 16:55:09 +02:00
MartinBraquet
6bc9e3d695 Downgrade next 2025-09-13 16:44:14 +02:00
MartinBraquet
b74fe59f12 Upgrade next 2025-09-13 16:19:23 +02:00
MartinBraquet
6b57aa7f14 Add info 2025-09-13 16:12:05 +02:00
MartinBraquet
227125b35c Update packages 2025-09-13 16:11:59 +02:00
MartinBraquet
c4012d8dfc Fix 2025-09-13 15:38:08 +02:00
MartinBraquet
cf3fa9ffbc Fix 2025-09-13 15:36:04 +02:00
MartinBraquet
40640d029a Update packages 2025-09-13 15:33:23 +02:00
MartinBraquet
01eb7038dc Add /support page 2025-09-13 15:12:45 +02:00
MartinBraquet
58115bfd11 Dynamic filename finding 2025-09-13 15:12:32 +02:00
MartinBraquet
f1ea5031fb Remove supabase token 2025-09-13 14:54:10 +02:00
MartinBraquet
26d15a9fb3 Remove autogenerated line 2025-09-13 13:14:56 +02:00
MartinBraquet
54ba8e6047 Upgrade next 2025-09-13 12:39:24 +02:00
MartinBraquet
eca063ab75 Clean 2025-09-13 12:14:17 +02:00
MartinBraquet
8892f4144e Remove logs 2025-09-13 12:14:09 +02:00
MartinBraquet
d2c25f9d6c Remove firebase warning 2025-09-13 12:03:08 +02:00
MartinBraquet
b57457dc2f Add yarn clean-install 2025-09-13 11:57:41 +02:00
MartinBraquet
2861b0cfa2 Update caniuse 2025-09-13 11:47:58 +02:00
MartinBraquet
0c45dbb884 Fix warning font 2025-09-13 11:46:58 +02:00
MartinBraquet
a9f9261fb7 Update description 2025-09-12 21:42:18 +02:00
MartinBraquet
7e5f54a4f1 Add anim in search bar 2025-09-12 21:40:02 +02:00
MartinBraquet
1228e8759c Add multiple keywords per search 2025-09-12 21:39:44 +02:00
MartinBraquet
1daf771218 Clean file architecture 2025-09-12 20:47:37 +02:00
MartinBraquet
880cb08c3d Update FAQ 2025-09-12 20:17:24 +02:00
MartinBraquet
e2cbae3089 Add supabase dev key 2025-09-12 20:06:20 +02:00
MartinBraquet
42dcc3318c Comment 2025-09-12 18:33:46 +02:00
MartinBraquet
b32a85ae7e Fix cookie 2025-09-12 18:27:39 +02:00
MartinBraquet
af85edddca Ignore pics 2025-09-12 18:18:55 +02:00
MartinBraquet
eccd88e3c2 Debug cookie 2025-09-12 18:14:34 +02:00
MartinBraquet
e0e11629a1 Set up PostHog 2025-09-12 18:03:21 +02:00
MartinBraquet
968095c183 Fix LOCAL_DEV import in shared 2025-09-12 17:40:54 +02:00
MartinBraquet
d32b5115c5 Clean images and SEO 2025-09-12 17:21:03 +02:00
MartinBraquet
d3001ec887 Fix 2025-09-12 17:00:53 +02:00
MartinBraquet
fef6a52008 Fix og url for local 2025-09-12 17:00:49 +02:00
MartinBraquet
048e6affbc Update dev info 2025-09-12 16:42:53 +02:00
MartinBraquet
c653d49691 Update doc 2025-09-12 15:56:42 +02:00
MartinBraquet
6f5c9bd054 Update docs 2025-09-12 15:55:45 +02:00
MartinBraquet
9e5576244d Fix readme 2025-09-12 15:36:23 +02:00
MartinBraquet
ef91317232 Add local dev info 2025-09-12 15:29:17 +02:00
MartinBraquet
10c44d050f Fix 2025-09-12 15:03:44 +02:00
MartinBraquet
1845ea7170 Fix 2025-09-12 15:03:12 +02:00
MartinBraquet
d453294622 Fix 2025-09-12 15:02:47 +02:00
MartinBraquet
d11f9e4971 Fix font 2025-09-12 15:01:57 +02:00
MartinBraquet
08272dd04e Fix typo 2025-09-12 15:01:50 +02:00
MartinBraquet
42441b9b42 Add info 2025-09-12 14:54:06 +02:00
MartinBraquet
e4a293c046 Add todos 2025-09-12 14:50:47 +02:00
MartinBraquet
0cc5a39d63 Add todos 2025-09-12 14:48:32 +02:00
MartinBraquet
942ea3f125 Update readme todo 2025-09-12 14:36:03 +02:00
MartinBraquet
a8a70bb71c Make avatar pic if no pictures 2025-09-12 12:01:04 +02:00
MartinBraquet
0d7c3fb4b2 Allow everyone to message everyone for now 2025-09-12 12:00:41 +02:00
MartinBraquet
77c682454e Reduce like button size 2025-09-12 12:00:15 +02:00
MartinBraquet
dd3473f5d8 Improve prompts link 2025-09-12 03:39:50 +02:00
MartinBraquet
cceadc5e04 Center the icons, even in gmail 2025-09-12 03:15:17 +02:00
MartinBraquet
e48c3a3f9c Add links to emails 2025-09-12 02:42:00 +02:00
MartinBraquet
14981ef077 Fix 2025-09-12 02:15:06 +02:00
MartinBraquet
a7858d44bd Fix empty content 2025-09-12 02:10:19 +02:00
MartinBraquet
9ae5f27c04 Remove free responses for now as not implemented in the db 2025-09-12 02:03:30 +02:00
MartinBraquet
d691129842 Hide location if null 2025-09-12 01:57:22 +02:00
MartinBraquet
e26d551263 Fix overrides 2025-09-12 01:47:11 +02:00
MartinBraquet
277c6a444f Release 2025-09-12 01:40:58 +02:00
MartinBraquet
f344800fd6 Fix yarn install warnings 2025-09-12 01:38:48 +02:00
MartinBraquet
39a6fba33f Remove unused and confusing sub lock files 2025-09-12 01:30:50 +02:00
MartinBraquet
8e11657bd2 Update install 2025-09-12 01:26:49 +02:00
MartinBraquet
dfbeaa4edf Clean lock 2025-09-12 01:23:16 +02:00
MartinBraquet
e90dc3b7f4 Remove log 2025-09-12 01:23:06 +02:00
MartinBraquet
dba89e611a Fix package backend 2025-09-12 01:17:39 +02:00
MartinBraquet
1a3fecc89e Fix 2025-09-12 00:46:38 +02:00
MartinBraquet
407e6a3d06 Fix yarn.lock 2025-09-12 00:40:46 +02:00
MartinBraquet
6ee19d5359 Back to working on vercel 2025-09-12 00:31:11 +02:00
MartinBraquet
2df424dbac Remove log 2025-09-12 00:14:23 +02:00
MartinBraquet
9874be6bf1 Fix packages 2025-09-12 00:10:50 +02:00
MartinBraquet
a3d4199d1d Fix vercel build 2025-09-11 23:56:41 +02:00
MartinBraquet
247fa146a9 Fix 2025-09-11 23:26:38 +02:00
MartinBraquet
f2b2c02cd6 Add install.sh 2025-09-11 22:56:29 +02:00
MartinBraquet
a915f27f00 Roolback 2025-09-11 22:56:21 +02:00
MartinBraquet
e14a488934 Revert "Failed attempt to use react icons in emails"
This reverts commit e82a8d9bc3.
2025-09-11 22:35:54 +02:00
MartinBraquet
e82a8d9bc3 Failed attempt to use react icons in emails 2025-09-11 22:35:48 +02:00
MartinBraquet
4527a0d12b Rm add tsconfig 2025-09-11 22:34:00 +02:00
MartinBraquet
01be202484 Cd cur dir 2025-09-11 19:20:40 +02:00
MartinBraquet
d1fe99edc3 Remove log 2025-09-11 19:20:32 +02:00
MartinBraquet
fa629591e9 Fix unsubscribe URL 2025-09-11 18:45:42 +02:00
MartinBraquet
4ab3edc97b Add email footer 2025-09-11 18:37:31 +02:00
MartinBraquet
f1bfc6bf55 Fix connection type (2) 2025-09-11 16:51:13 +02:00
MartinBraquet
3283843ef3 Fix connection type 2025-09-11 16:21:01 +02:00
MartinBraquet
4cb14ec8cc Fix gender 2025-09-11 16:20:11 +02:00
MartinBraquet
41535a68be Improve email UI 2025-09-11 16:00:10 +02:00
MartinBraquet
d62447a12a Unstick like 2025-09-11 14:48:36 +02:00
MartinBraquet
802367c914 Search users 2025-09-11 14:48:24 +02:00
MartinBraquet
ff9b2c6ee8 Add compatibility score FAQ 2025-09-11 14:12:44 +02:00
MartinBraquet
a0e25c941a Smoot login 2025-09-11 13:59:23 +02:00
MartinBraquet
091c99e784 Reduce sidebar width 2025-09-11 13:27:44 +02:00
MartinBraquet
e264bb407b Improve sign in / up UI 2025-09-11 13:14:20 +02:00
MartinBraquet
16625210fc Fix 2025-09-11 12:51:37 +02:00
MartinBraquet
2550453ee4 Add keyword search 2025-09-10 22:50:20 +02:00
MartinBraquet
d1c480f23f Change genders 2025-09-10 21:54:03 +02:00
MartinBraquet
b4b0397589 Hide gender they are interested in 2025-09-10 21:53:55 +02:00
MartinBraquet
ab6b34e84c Hide supabase annon key (env) 2025-09-10 21:41:08 +02:00
MartinBraquet
87af9d5078 Hide supabase annon key 2025-09-10 21:40:31 +02:00
MartinBraquet
95fab7c395 Add reports table 2025-09-10 21:40:23 +02:00
MartinBraquet
90825925ff Improve share button layout 2025-09-10 21:39:57 +02:00
MartinBraquet
7036cf9e49 Profile view when signed up: no pic, see first lines of bio 2025-09-10 20:42:09 +02:00
MartinBraquet
53123eb0ee Improve UI 2025-09-10 18:24:44 +02:00
MartinBraquet
3c5407dd51 Fix colors 2025-09-10 17:09:35 +02:00
MartinBraquet
1ffe81f740 Fix link 2025-09-10 16:20:50 +02:00
MartinBraquet
6bb35d61e1 Clean 2025-09-10 16:16:11 +02:00
MartinBraquet
f36ccf7bdc Fix colors 2025-09-10 16:16:08 +02:00
MartinBraquet
4632e68a00 Add bookish font 2025-09-10 16:14:56 +02:00
MartinBraquet
09858d0783 Change md path 2025-09-10 16:14:33 +02:00
MartinBraquet
9d1423c41b Add todo 2025-09-10 14:29:11 +02:00
MartinBraquet
1a4b7786dd Fix blue links 2025-09-10 12:53:34 +02:00
MartinBraquet
77c0a21ad0 Add info about Martin 2025-09-10 12:23:33 +02:00
MartinBraquet
7cedf14121 Add members page 2025-09-10 12:23:25 +02:00
MartinBraquet
235346f3dd Add financials link 2025-09-10 11:26:14 +02:00
MartinBraquet
34c36b7c3a Fix 2025-09-09 19:24:08 +02:00
MartinBraquet
3e0f788ec3 Add FAQ and financials 2025-09-09 19:18:44 +02:00
MartinBraquet
867bb8a072 Fix 2025-09-09 18:55:47 +02:00
MartinBraquet
31a400158a Do not render your own profile in Profiles 2025-09-09 16:58:07 +02:00
MartinBraquet
8106ff6489 Show compatibility score of no preferred gender 2025-09-09 16:57:02 +02:00
MartinBraquet
de3508993c Factor out geodbFetch 2025-09-09 16:07:36 +02:00
MartinBraquet
fd3e7a6f8a Fix location filtering 2025-09-09 15:55:33 +02:00
MartinBraquet
4cf97a6054 Fix 2025-09-09 15:25:21 +02:00
MartinBraquet
75036e3ec7 Fix 2025-09-09 14:57:08 +02:00
MartinBraquet
a157f6ce27 Release 2025-09-09 14:50:36 +02:00
MartinBraquet
d27cc94dd0 Update readme 2025-09-09 14:48:47 +02:00
MartinBraquet
05b1416d39 Fix link 2025-09-09 13:58:41 +02:00
MartinBraquet
001d6ed968 Add constitution 2025-09-09 13:53:24 +02:00
MartinBraquet
3ed3eecb00 Ignore test 2025-09-09 13:30:24 +02:00
MartinBraquet
8638e6cdeb Clean about blocks 2025-09-09 13:28:26 +02:00
MartinBraquet
15c7a9c22e Add react-md 2025-09-09 03:31:58 +02:00
MartinBraquet
20566e42ec Fix sign in loading page 2025-09-09 03:25:14 +02:00
MartinBraquet
92d1cac254 Fix sign up loading page 2025-09-09 03:03:02 +02:00
MartinBraquet
72e879c424 Add terms and privacy notices 2025-09-09 02:08:12 +02:00
Martin Braquet
3b2516fea2 Create NOTICE 2025-09-09 01:44:06 +02:00
Martin Braquet
1151f98fd3 Update LICENSE-MIT 2025-09-09 01:42:54 +02:00
Martin Braquet
d608fdb6f0 Update LICENSE-MIT 2025-09-09 01:42:33 +02:00
Martin Braquet
5a2c172f96 Update LICENSE-MIT 2025-09-09 01:38:40 +02:00
Martin Braquet
6e1f7bdd7b Update LICENSE-MIT 2025-09-09 01:38:21 +02:00
Martin Braquet
bf96227b8b Update LICENSE-MIT 2025-09-09 01:35:38 +02:00
MartinBraquet
e7195dd68d Log 2025-09-09 01:16:14 +02:00
MartinBraquet
6a374b0c5a Fix compatibility questions 2025-09-08 23:46:50 +02:00
MartinBraquet
a0a876a282 Fix link going over max width 2025-09-08 23:12:41 +02:00
MartinBraquet
0a538375b2 Fix hanging button 2025-09-08 23:03:57 +02:00
MartinBraquet
ac5d60cd58 Add home line 2025-09-08 22:54:01 +02:00
MartinBraquet
8498480b9c Clean forms and prevent from changing username after creation 2025-09-08 22:35:57 +02:00
MartinBraquet
4c1c7fc514 Remove onlyfans (2)... 2025-09-08 22:35:33 +02:00
MartinBraquet
1c3a0f9c71 Remove onlyfans... 2025-09-08 22:35:28 +02:00
MartinBraquet
39b5068370 Fix tracker 2025-09-08 22:34:50 +02:00
MartinBraquet
4815cc7682 Make all complete-profile fields optional and Pre-Save lover 2025-09-07 23:54:37 +02:00
MartinBraquet
7886d32933 Refactor 2025-09-07 21:40:56 +02:00
MartinBraquet
2dab88c7a9 Rollback packages 2025-09-07 21:33:44 +02:00
MartinBraquet
35b83dcb9a Fix 2025-09-07 21:28:37 +02:00
MartinBraquet
5194b5f6bf Clean home and fix profiles not loading 2025-09-07 21:12:06 +02:00
MartinBraquet
d7c49fe19f Refactor into AboutBox 2025-09-07 20:39:09 +02:00
MartinBraquet
e3dadd2ce8 Fix dynamic "search" 2025-09-07 20:28:50 +02:00
MartinBraquet
d2d08bc77c Update packages 2025-09-07 20:04:56 +02:00
MartinBraquet
41da374d93 Fix github CI 2025-09-07 16:23:24 +02:00
MartinBraquet
c2f48fc90c Fix scaling 2025-09-07 16:16:57 +02:00
MartinBraquet
18f2b61545 Render bio and name only in profiles grid 2025-09-07 16:15:01 +02:00
MartinBraquet
a71c7adaf7 Move mutual likes down 2025-09-06 20:33:42 +02:00
MartinBraquet
3c050bee3b Show previous profile while loading another one 2025-09-06 20:30:46 +02:00
MartinBraquet
50be1ba510 Add logs 2025-09-06 20:13:44 +02:00
MartinBraquet
445f62ca53 Put profile pic down 2025-09-06 20:13:17 +02:00
MartinBraquet
4810904aa8 Add logs 2025-09-06 20:13:05 +02:00
MartinBraquet
3a6d459ebd Skip jwt supabase update 2025-09-06 20:12:37 +02:00
MartinBraquet
5c23380de9 Clean 2025-09-06 19:35:13 +02:00
MartinBraquet
a0285d970f Clean 2025-09-06 19:34:52 +02:00
MartinBraquet
0e7e0f52f1 Fix 2025-09-06 15:56:54 +02:00
MartinBraquet
5054a9552b Update env.example 2025-09-06 15:56:47 +02:00
MartinBraquet
a7c55530a4 Hide and regenerate Firebase API key 2025-09-06 14:58:30 +02:00
MartinBraquet
0f03746c6a Fix register redirect and add error message for email already in use 2025-09-06 12:02:06 +02:00
MartinBraquet
4a891d9c9a Fix 2025-09-06 11:31:27 +02:00
MartinBraquet
209e233ee2 Add error message 2025-09-06 11:29:37 +02:00
MartinBraquet
519ec081b5 Add 404 info 2025-09-06 11:07:49 +02:00
MartinBraquet
a73c7ff8b6 Fix 2025-09-06 10:53:42 +02:00
MartinBraquet
ea74e0514e Make sign in and sign up pages and allow for email/pwd registration 2025-09-06 10:50:47 +02:00
MartinBraquet
523bbd11cc Update readme 2025-09-05 20:29:01 +02:00
MartinBraquet
becf6ad7a4 Fix wrong firebase bucket name 2025-09-03 12:00:15 +02:00
MartinBraquet
364f58b186 Fix 2025-09-01 22:54:52 +02:00
MartinBraquet
ef36b78399 Comment 2025-09-01 22:51:34 +02:00
MartinBraquet
1c77e6dc2c Hide profiles if not logged in and put nice home page 2025-09-01 22:46:02 +02:00
MartinBraquet
d581ce054c Clean more files 2025-09-01 22:19:23 +02:00
MartinBraquet
2956ec073f Add about in mobile 2025-09-01 22:19:14 +02:00
MartinBraquet
3ace876b66 Clean text 2025-09-01 22:19:01 +02:00
MartinBraquet
d8722a8274 Clean NEXT_PUBLIC_FIREBASE_ENV 2025-09-01 22:18:48 +02:00
MartinBraquet
d5216a8e8c Remove unused component 2025-09-01 18:29:29 +02:00
MartinBraquet
152521e9e5 Fix color style 2025-09-01 18:27:58 +02:00
MartinBraquet
a2d8518f14 Even nicer /about 2025-09-01 18:21:16 +02:00
MartinBraquet
e88a384c5e Get nice about page 2025-09-01 18:12:41 +02:00
MartinBraquet
063663d5b0 Fix discord link 2025-09-01 17:54:15 +02:00
MartinBraquet
0a48263013 Remove supabase log 2025-09-01 17:54:05 +02:00
MartinBraquet
2877bc8239 Fix old about 2025-09-01 17:51:14 +02:00
MartinBraquet
d5886fe3f2 Remove pink border 2025-09-01 17:50:58 +02:00
MartinBraquet
c0bacb104f Clean 2025-09-01 17:45:53 +02:00
MartinBraquet
27b851dca1 Clean 2025-09-01 17:40:38 +02:00
MartinBraquet
bd358b38f7 Fix 2025-09-01 17:33:53 +02:00
MartinBraquet
f6c895fe78 Clean manifold names and update discord invite and improve email notifs 2025-09-01 17:30:45 +02:00
MartinBraquet
23c8f175bb Fix supabase IPV6 only host 2025-09-01 16:49:53 +02:00
MartinBraquet
2949871ba1 Fixes 2025-09-01 15:01:23 +02:00
MartinBraquet
34298fcfa1 Clean 2025-09-01 14:58:29 +02:00
MartinBraquet
83c6973d8e Add setup script 2025-09-01 14:56:23 +02:00
MartinBraquet
3b932fd52d Remove build 2025-09-01 14:39:49 +02:00
MartinBraquet
e0291b8e5a Fix tests 2025-09-01 14:36:47 +02:00
MartinBraquet
8fe3736411 Add base tests 2025-09-01 14:33:04 +02:00
MartinBraquet
70e46c2b69 Upgrade API deploy script 2025-09-01 14:17:21 +02:00
MartinBraquet
c5a7d823c8 Upgrade API readme 2025-09-01 14:17:09 +02:00
MartinBraquet
56115d34a4 Fix SSL 2025-09-01 14:16:57 +02:00
MartinBraquet
af3b91037e Copy tsconfig to docker 2025-09-01 14:16:30 +02:00
MartinBraquet
549161586e Fix tsc-alias 2025-09-01 14:15:31 +02:00
MartinBraquet
20ac60219f Add version 2025-09-01 14:15:14 +02:00
MartinBraquet
ef8b17f5c1 Source from .env 2025-09-01 14:15:06 +02:00
MartinBraquet
2c5339aa92 Use supabase ipv4 2025-09-01 14:14:41 +02:00
MartinBraquet
720fa70d60 Use prod by default 2025-09-01 14:14:17 +02:00
MartinBraquet
b2eef1279f use LOCAL_DEV 2025-09-01 14:14:07 +02:00
MartinBraquet
da5e5aedb2 rename secret 2025-09-01 14:13:35 +02:00
MartinBraquet
51c505f9a6 Update lock 2025-09-01 14:13:18 +02:00
MartinBraquet
4f97a61e94 Remove package lock as using yarn lock 2025-09-01 14:13:09 +02:00
MartinBraquet
7af0f28bd7 Acknowledge manifold.love 2025-08-31 12:17:46 +02:00
MartinBraquet
a9f4e95b77 Set up google cloud server 2025-08-28 22:16:42 +02:00
MartinBraquet
3d3420b1aa Add lock file 2025-08-28 17:57:03 +02:00
MartinBraquet
26915ea94f Fix vercel env key 2025-08-28 17:29:03 +02:00
MartinBraquet
dfa1d1c76e Remove pwd 2025-08-28 16:56:46 +02:00
MartinBraquet
87c1870770 Make PROD work 2025-08-28 16:42:51 +02:00
MartinBraquet
626c27b635 Fix color 2025-08-28 12:49:09 +02:00
MartinBraquet
7572d1a6ff Add email sending 2025-08-28 12:49:05 +02:00
MartinBraquet
b707439de7 Rename to Compass (2) 2025-08-27 22:17:07 +02:00
MartinBraquet
2e332707ff Rename to Compass 2025-08-27 22:12:04 +02:00
MartinBraquet
2f60efe273 Set grey color scales 2025-08-27 22:07:43 +02:00
MartinBraquet
632f8477fd Fix favicon 2025-08-27 22:07:35 +02:00
MartinBraquet
53432520cd Pull up features from manifold.love 2025-08-27 21:30:05 +02:00
MartinBraquet
078893f7d1 Add books feature 2025-08-11 17:38:26 +02:00
MartinBraquet
131cb0ff79 Disable interest cache 2025-08-11 17:34:00 +02:00
MartinBraquet
c49117ffd5 Rename about 2025-08-11 15:40:08 +02:00
MartinBraquet
cb52aa264d Update desc 2025-08-11 15:09:39 +02:00
MartinBraquet
1c1a02757f Add loading... info 2025-08-10 13:04:29 +02:00
MartinBraquet
4558c4a29c Make social style slider in /complete-profile 2025-08-10 12:59:09 +02:00
MartinBraquet
21832a1885 Update introversion bar 2025-08-09 22:19:05 +02:00
MartinBraquet
9b45cc087f Fix 2025-08-09 21:56:52 +02:00
MartinBraquet
b131e6ee8c Hide minor onboarding questions 2025-08-09 21:51:01 +02:00
MartinBraquet
306a297837 Change connections options 2025-08-09 21:50:38 +02:00
MartinBraquet
380eda64a0 Update schema.prisma 2025-08-08 01:51:29 +02:00
MartinBraquet
2dd8e3016f Undo schema 2025-08-08 01:43:55 +02:00
MartinBraquet
fd9c61a1c7 Redirect new users to onboarding page 2025-08-08 01:19:36 +02:00
MartinBraquet
4a2dba6e2e Hide register buttons if logged in 2025-08-07 23:28:10 +02:00
MartinBraquet
034d94ee22 Move sign in button from header to /profile 2025-08-07 23:16:07 +02:00
MartinBraquet
65139094cb Edit readme 2025-08-07 23:03:02 +02:00
emilyokeefe
b607cf22a7 More concise wording on the about page (#5)
* Changed wording

* remove share button because it doesn't function yet

* more concise wording
2025-08-07 19:16:10 +02:00
emilyokeefe
b79f8d05be Added onboarding questions and worked on style (#3) 2025-08-07 13:52:19 +02:00
Martin Braquet
520d157a0f Move LICENSE from MIT to AGPL 2025-08-07 13:39:33 +02:00
MartinBraquet
f73be7e38f Delete license 2025-08-07 13:32:22 +02:00
MartinBraquet
44834d1d27 Hide registration options until 3 chars typed 2025-08-07 01:00:25 +02:00
MartinBraquet
d07422060a Add local dev prisma db 2025-08-07 00:35:22 +02:00
emilyokeefe
0ee9437cf8 Changed wording (#4)
* Changed wording

* remove share button because it doesn't function yet

---------

Co-authored-by: Martin Braquet <martin.braquet@gmail.com>
2025-08-06 23:05:10 +02:00
MartinBraquet
6fe646a32b Hide stats for now 2025-08-06 14:16:21 +02:00
MartinBraquet
8e1f643612 Add basic multi-step form 2025-08-05 20:19:11 +02:00
MartinBraquet
7f2ba4d727 Fix test 2025-08-05 17:47:40 +02:00
MartinBraquet
b8706eae10 Clean 2025-08-05 17:42:21 +02:00
MartinBraquet
34600ab0cf Fix favicon 2025-08-05 17:33:35 +02:00
MartinBraquet
b3031b79d1 Restrict userbase access to logged-in users 2025-08-05 17:12:53 +02:00
MartinBraquet
cc679ddcfa Fix image 2025-08-05 17:11:55 +02:00
MartinBraquet
3633d469c1 Switch from home to logo on narrow screens 2025-08-05 16:46:49 +02:00
MartinBraquet
46fa721a6c Add prisma config 2025-08-05 16:26:57 +02:00
emilyokeefe
7e4116b1a2 UI redesign: Add compass branding, improve navigation, & simplify content (#1)
* UI redesign: Add compass branding, improve navigation, and simplify content

* Add homepage improvements
- Created "Why Compass" section with three value props
- Worked on spacing
- Made word "Search" styled with typing effect

* Delete pic

* Fix lint

* Compress favicon

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
2025-08-05 16:14:07 +02:00
MartinBraquet
2a043dbd53 Add AND multi keyword search 2025-08-05 02:18:37 +02:00
MartinBraquet
4aa23cf755 Fix 2025-08-05 01:59:24 +02:00
MartinBraquet
4f261116de Add filter params to url query params 2025-08-05 01:55:31 +02:00
MartinBraquet
7ecd5481a0 Move button 2025-08-04 23:21:03 +02:00
MartinBraquet
1cea1b7bcf Hide cause areas 2025-08-04 23:09:26 +02:00
MartinBraquet
ccba688ec4 Rename 2025-08-04 22:55:29 +02:00
MartinBraquet
4eabe72078 Remove conflict style 2025-08-04 22:46:28 +02:00
MartinBraquet
fbbe97d297 Add sign in 2025-08-04 22:42:15 +02:00
MartinBraquet
26a80d1313 Change description 2025-08-04 22:19:55 +02:00
MartinBraquet
24b5edba9f Remove age slider 2025-08-04 22:14:16 +02:00
MartinBraquet
9113e0e372 Update name 2025-08-04 22:09:14 +02:00
MartinBraquet
9f944dd171 Clean texts 2025-08-04 19:34:50 +02:00
MartinBraquet
b91f76914c Rename manifesto 2025-08-04 19:31:20 +02:00
MartinBraquet
8593d90209 Remove core 2025-08-04 19:30:41 +02:00
MartinBraquet
df441b7236 Remove age slider 2025-08-04 19:29:04 +02:00
MartinBraquet
43e2798a9b Rename desired connection 2025-08-04 19:27:38 +02:00
MartinBraquet
7ee14a48a7 Change search bar placeholder 2025-08-04 19:24:42 +02:00
MartinBraquet
1e5cf0ca5b Move user count 2025-08-04 19:24:30 +02:00
MartinBraquet
12bac6d305 Fix spinner 2025-08-04 19:18:35 +02:00
MartinBraquet
1518cd50ec Clean 2025-08-04 18:46:33 +02:00
MartinBraquet
9dfc82c106 Clean 2025-08-04 14:43:23 +02:00
MartinBraquet
087f10f7bb Rename 2025-08-04 14:39:43 +02:00
MartinBraquet
6971eac21f Add badges 2025-08-04 14:38:19 +02:00
MartinBraquet
da7cde91b3 Fix 2025-08-04 14:28:24 +02:00
MartinBraquet
814b4fe0ae Add tests 2025-08-04 14:25:44 +02:00
MartinBraquet
a2abc4fda9 Fix bad useState practices 2025-08-04 14:25:33 +02:00
MartinBraquet
9bcba9895e Clean bar info 2025-08-04 14:24:54 +02:00
MartinBraquet
ff13ea71a6 Fix spinner 2025-08-04 14:24:03 +02:00
MartinBraquet
7993dcb0b1 Update gitig 2025-08-04 14:23:42 +02:00
MartinBraquet
9a527fef20 Update lint 2025-08-04 14:23:30 +02:00
MartinBraquet
e6de25c0a4 Fix some lints 2025-08-04 11:44:24 +02:00
MartinBraquet
c45adc1a8a Remove slack 2025-08-04 11:34:17 +02:00
MartinBraquet
c06f86edbb Fix loading spinner 2025-08-04 11:34:01 +02:00
MartinBraquet
ed515fa3fc Add base tests 2025-08-04 10:26:19 +02:00
MartinBraquet
c284983b4b Update dev docs 2025-08-04 10:16:24 +02:00
MartinBraquet
5fed099034 Update README.md 2025-08-04 10:16:19 +02:00
MartinBraquet
c8dd335d65 Update README.md 2025-08-04 09:58:51 +02:00
MartinBraquet
17d2c6aa57 Clean 2025-08-04 09:56:30 +02:00
MartinBraquet
7183144d7b Fix release.sh perm 2025-08-04 09:54:27 +02:00
MartinBraquet
e87a2e9c8c Major release 2025-08-04 09:50:23 +02:00
MartinBraquet
b8da78f2fa Add cd 2025-08-04 09:50:14 +02:00
MartinBraquet
528402437b Clean 2025-08-04 09:50:07 +02:00
1082 changed files with 114172 additions and 18067 deletions

View File

@@ -0,0 +1,300 @@
---
apply: by model decision
---
---
trigger: always_on
description:
globs:
---
## Project Structure
Compass (compassmeet.com) is a transparent dating platform for forming deep, authentic 1-on-1 connections.
- **Next.js React frontend** `/web`
- Pages, components, hooks, lib
- **Express Node API server** `/backend/api`
- **Shared backend utilities** `/backend/shared`
- **Email functions** `/backend/email`
- **Database schema** `/backend/supabase`
- Supabase-generated types in `/backend/supabase/schema.ts`
- **Files shared between frontend and backend** `/common`
- Types (User, Profile, etc.) and utilities
- Try not to add package dependencies to common
- **Android app** `/android`
## Deployment
- Both dev and prod environments
- Backend on GCP (Google Cloud Platform)
- Frontend on Vercel
- Database on Supabase (PostgreSQL)
- Firebase for authentication and storage
## Code Guidelines
### Component Example
```tsx
import clsx from 'clsx'
import Link from 'next/link'
import {User} from 'common/user'
import {ProfileRow} from 'common/profiles/profile'
import {useUser} from 'web/hooks/use-user'
import {useT} from 'web/lib/locale'
interface ProfileCardProps {
user: User
profile: ProfileRow
}
export function ProfileCard({user, profile}: ProfileCardProps) {
const t = useT()
return (
<div className={clsx('bg-canvas-50 rounded-lg p-4')}>
<img src={user.avatarUrl} alt={user.name} />
<h3>{user.name}</h3>
<p>{profile.bio}</p>
</div>
)
}
```
We prefer many smaller components that each represent one logical unit, rather than one large component.
Export the main component at the top of the file. Name the component the same as the file (e.g., `profile-card.tsx`
`ProfileCard`).
### API Calls
**Server-side (getStaticProps):**
```typescript
import {api} from 'web/lib/api'
export async function getStaticProps() {
const profiles = await api('get-profiles', {})
return {
props: {profiles},
revalidate: 30 * 60, // 30 minutes
}
}
```
**Client-side - use hooks:**
```typescript
import {useAPIGetter} from 'web/hooks/use-api-getter'
function ProfileList() {
const {data, refresh} = useAPIGetter('get-profiles', {})
if (!data) return <Loading / >
return (
<div>
{
data.profiles.map((profile) => (
<ProfileCard key = {profile.id} user = {profile.user} profile = {profile}
/>
))
}
<button onClick = {refresh} > Refresh < /button>
< /div>
)
}
```
### Database Access
**Backend (pg-promise):**
```typescript
import {createSupabaseDirectClient} from 'shared/supabase/init'
const pg = createSupabaseDirectClient()
const user = await pg.oneOrNone<User>('SELECT * FROM users WHERE username = $1', [username])
```
**Frontend (Supabase client):**
```typescript
import {db} from 'web/lib/supabase/db'
const {data} = await db.from('profiles').select('*').eq('user_id', userId)
```
### Translation
```typescript
import {useT} from 'web/lib/locale'
function MyComponent() {
const t = useT()
return <h1>{t('welcome', 'Welcome to Compass'
)
}
</h1>
}
```
Translation files are in `common/messages/` (en.json, fr.json, de.json).
### Backend Endpoints
1. Define schema in `common/src/api/schema.ts`:
```typescript
'get-user-and-profile'
:
{
method: 'GET',
authed
:
false,
rateLimited
:
true,
props
:
z.object({
username: z.string().min(1),
}),
returns
:
{
}
as
{
user: User;
profile: ProfileRow | null
}
,
summary: 'Get user and profile data by username',
tag
:
'Users',
}
,
```
2. Create handler in `backend/api/src/`:
```typescript
import {APIErrors, APIHandler} from './helpers/endpoint'
export const getUserAndProfile: APIHandler<'get-user-and-profile'> = async ({username}, _auth) => {
const user = await getUserByUsername(username)
if (!user) {
throw APIErrors.notFound('User not found')
}
return {user, profile}
}
```
3. Register in `backend/api/src/app.ts`:
```typescript
import {getUserAndProfile} from './get-user-and-profile'
const handlers = {
'get-user-and-profile': getUserAndProfile,
// ...
}
```
### Profile Options (Interests, Causes, Work)
Options are stored in separate tables with many-to-many relationships:
- `interests`, `causes`, `work` - option values
- `profile_interests`, `profile_causes`, `profile_work` - junction tables
Fetch in parallel:
```typescript
const [interestsRes, causesRes, workRes] = await Promise.all([
db.from('profile_interests').select('interests(name, id)').eq('profile_id', profile.id),
db.from('profile_causes').select('causes(name, id)').eq('profile_id', profile.id),
db.from('profile_work').select('work(name, id)').eq('profile_id', profile.id),
])
```
## Testing
### Running Tests
```bash
# Jest (unit + integration)
yarn test
# Playwright (E2E)
yarn test:e2e
```
### Test Structure
- Unit tests: `*.unit.test.ts` in `tests/unit/`
- Integration tests: `*.integration.test.ts` in `tests/integration/`
- E2E tests: `*.e2e.spec.ts` in `tests/e2e/`
### Mocking Example
```typescript
jest.mock('shared/supabase/init')
import {createSupabaseDirectClient} from 'shared/supabase/init'
const mockPg = {
oneOrNone: jest.fn(),
tx: jest.fn(async (cb) => cb(mockTx)),
}
;(createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
```
## Important Patterns
### User Registration
- Create user + profile + options in single database transaction
- Return full profile data from creation API
- Don't use sleep() hacks - rely on transactional integrity
### API Errors
```typescript
import {APIErrors} from './helpers/endpoint'
throw APIErrors.notFound('User not found')
throw APIErrors.badRequest('Invalid input', {field: 'email'})
```
### Logging
- Use `debug()` from `common/logger` for development
- Use `log` from `shared/utils` for production
## Things to Avoid
- Don't use string concatenation for SQL queries
- Don't add sleep() delays for "eventual consistency"
- Don't create separate API calls when data can be batched in one transaction
- Don't use console.log - use `debug()` or `log()`
## Key Dependencies
- Node.js 20+
- React 19
- Next.js 16
- Supabase (PostgreSQL)
- Firebase (Auth, Storage)
- Tailwind CSS
- Jest (testing)
- Playwright (E2E testing)

120
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,120 @@
# Enables IDE autocompletion for this config file
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
# Language for CodeRabbit's review comments
language: en
# Enable experimental features (currently not using any specific early_access features)
early_access: true
chat:
# CodeRabbit will automatically respond to @coderabbitai mentions in PR comments
auto_reply: true
reviews:
auto_review:
# Automatically trigger reviews when PRs are opened or updated
enabled: true
# Skip auto-review if PR title contains these keywords
ignore_title_keywords:
- "WIP"
# Don't auto-review draft PRs
drafts: false
# Only auto-review PRs targeting these branches
base_branches:
- main
- develop
# Include a high-level summary at the start of each review
high_level_summary: true
# Generate sequence diagrams for complex code flows
sequence_diagrams: true
# Don't include poems in reviews (fun feature, but keeping it professional)
poem: false
# Show review completion status
review_status: true
# Keep the walkthrough section expanded by default
collapse_walkthrough: false
# Include summary of all changed files
changed_files_summary: true
# Don't automatically request changes on the PR (just leave comments)
request_changes_workflow: false
# Pre-merge checks to enforce before merging PRs
pre_merge_checks:
description:
# Validate that PR has a proper description
mode: warning # Options: off, warning, error
docstrings:
# Disable docstring coverage checks (let's assume we don't need them)
mode: off
# Exclude these paths from reviews (build artifacts and dependencies)
path_filters:
- "!**/node_modules/**" # npm dependencies
- "!**/android/**" # Native Android build files
- "!**/ios/**" # Native iOS build files
- "!**/.expo/**" # Expo build cache
- "!**/.expo-shared/**" # Expo shared config
- "!**/dist/**" # Build output
# Custom review instructions for specific file patterns
path_instructions:
# TypeScript/JavaScript files - main app code
- path: "**/*.{ts,tsx,js,jsx}"
instructions: |
General practices:
- Summarize the changes clearly.
- Format the summary with bullet points.
- Highlight any potential breaking changes for users.
- We use early returns to avoid deep nesting.
- Ensure all public functions have docstrings.
- Flag any hardcoded strings; they should be in the constants file.
- Check for edge cases like null values or empty arrays.
- Suggest performance optimizations where appropriate.
Mobile best practices:
- Proper use of hooks (useRouter, useFonts, useAssets)
- Accessibility: touch targets min 44x44, screen reader support
- Safe area handling and platform-specific code (iOS vs Android)
- Memory leaks in useEffect and event listeners
Performance:
- Use FlatList/SectionList for lists (never ScrollView with .map)
- React.memo, useMemo, useCallback where appropriate
TypeScript:
- Avoid 'any', use explicit types
- Prefer 'import type' for type imports
Security:
- No exposed API keys or sensitive data
- Use expo-secure-store for sensitive storage
- Validate deep linking configurations
Internationalization:
- User-visible strings should be externalized to JSON resource files in common/messages via
```
const t = useT()
const message = t('key', 'english string')
```
- path: "tests/e2e/**/*.ts"
instructions: |
Playwright E2E test guidelines for this repo:
- Page objects live in `tests/e2e/web/pages/`. Each class wraps one page/route, holds only `private readonly` Locators, and exposes action methods.
- All tests must use the `app` fixture (type `App`) from `tests/e2e/web/fixtures/base.ts`. Never instantiate page objects directly in a test.
- Cross-page flows (actions spanning multiple pages) belong as methods on the `App` class, not as standalone helper functions.
- Action methods in page objects must assert `expect(locator).toBeVisible()` before interacting.
- Never use `page.waitForTimeout()`. Use Playwright's built-in auto-waiting or `waitForURL` / `waitForSelector`.
- No hardcoded credentials in spec files; use `SPEC_CONFIG.ts` or account fixtures.
- Test account cleanup must be done in fixture teardown (after `await use(...)`), not in `afterEach` hooks.
- File and class names must use PascalCase (e.g., `CompatibilityPage.ts` / `class CompatibilityPage`).
- No DB or Firebase calls inside page object classes; those belong in `tests/e2e/utils/`.
- Flag any new page object not yet registered in `App`.

5
.coderabbitignore Normal file
View File

@@ -0,0 +1,5 @@
**/*.md
package-lock.json
yarn.lock
dist/**
test/mocks/**

420
.cursor/rules/dev-rules.mdc Normal file
View File

@@ -0,0 +1,420 @@
---
alwaysApply: true
---
## Project Structure
- next.js react tailwind frontend `/web`
- broken down into pages, components, hooks, lib
- express node api server `/backend/api`
- one off scripts, like migrations `/backend/scripts`
- supabase postgres. schema in `/backend/supabase`
- supabase-generated types in `/backend/supabase/schema.ts`
- files shared between backend directories `/backend/shared`
- anything in `/backend` can import from `shared`, but not vice versa
- files shared between the frontend and backend in `/common`
- `/common` has lots of type definitions for our data structures, like User. It also contains many useful utility functions. We try not to add package dependencies to common. `/web` and `/backend` are allowed to import from `/common`, but not vice versa.
## Deployment
- The project has both dev and prod environments.
- Backend is on GCP (Google Cloud Platform). Deployment handled by terraform.
- Project ID is `compass-130ba`.
## Code Guidelines
---
Here's an example component from web in our style:
```tsx
import clsx from 'clsx'
import Link from 'next/link'
import { isAdminId, isModId } from 'common/envs/constants'
import { type Headline } from 'common/news'
import { EditNewsButton } from 'web/components/news/edit-news-button'
import { Carousel } from 'web/components/widgets/carousel'
import { useUser } from 'web/hooks/use-user'
import { track } from 'web/lib/service/analytics'
import { DashboardEndpoints } from 'web/components/dashboard/dashboard-page'
import { removeEmojis } from 'common/util/string'
export function HeadlineTabs(props: {
headlines: Headline[]
currentSlug: string
endpoint: DashboardEndpoints
hideEmoji?: boolean
notSticky?: boolean
className?: string
}) {
const { headlines, endpoint, currentSlug, hideEmoji, notSticky, className } =
props
const user = useUser()
return (
<div
className={clsx(
className,
'bg-canvas-50 w-full',
!notSticky && 'sticky top-0 z-50'
)}
>
<Carousel labelsParentClassName="gap-px">
{headlines.map(({ id, slug, title }) => (
<Tab
key={id}
label={hideEmoji ? removeEmojis(title) : title}
href={`/${endpoint}/${slug}`}
active={slug === currentSlug}
/>
))}
{user && <Tab label="More" href="/dashboard" />}
{user && (isAdminId(user.id) || isModId(user.id)) && (
<EditNewsButton endpoint={endpoint} defaultDashboards={headlines} />
)}
</Carousel>
</div>
)
}
```
---
We prefer to have many smaller components that each represent one logical unit, rather than one very large component that does everything. Then we compose and reuse the components.
It's best to export the main component at the top of the file. We also try to name the component the same as the file name (headline-tabs.tsx) so that it's easy to find.
Here's another example in `home.tsx` that calls our api. We have an endpoint called 'headlines', which is being cached by NextJS:
```ts
import { api } from 'web/lib/api/api'
// More imports...
export async function getStaticProps() {
try {
const headlines = await api('headlines', {})
return {
props: {
headlines,
revalidate: 30 * 60, // 30 minutes
},
}
} catch (err) {
return { props: { headlines: [] }, revalidate: 60 }
}
}
export default function Home(props: { headlines: Headline[] }) { ... }
```
---
If we are calling the API on the client, prefer using the `useAPIGetter` hook:
```ts
export const YourTopicsSection = (props: {
user: User
className?: string
}) => {
const { user, className } = props
const { data, refresh } = useAPIGetter('get-followed-groups', {
userId: user.id,
})
const followedGroups = data?.groups ?? []
...
```
This stores the result in memory, and allows you to call refresh() to get an updated version.
---
We frequently use `usePersistentInMemoryState` or `usePersistentLocalState` as an alternative to `useState`. These cache data. Most of the time you want in-memory caching so that navigating back to a page will preserve the same state and appear to load instantly.
Here's the definition of usePersistentInMemoryState:
```ts
export const usePersistentInMemoryState = <T>(initialValue: T, key: string) => {
const [state, setState] = useStateCheckEquality<T>(
safeJsonParse(store[key]) ?? initialValue
)
useEffect(() => {
const storedValue = safeJsonParse(store[key]) ?? initialValue
setState(storedValue as T)
}, [key])
const saveState = useEvent((newState: T | ((prevState: T) => T)) => {
setState((prevState) => {
const updatedState = isFunction(newState) ? newState(prevState) : newState
store[key] = JSON.stringify(updatedState)
return updatedState
})
})
return [state, saveState] as const
}
```
---
For live updates, we use websockets. In `use-api-subscription.ts`, we have this hook:
```ts
export function useApiSubscription(opts: SubscriptionOptions) {
useEffect(() => {
const ws = client
if (ws != null) {
if (opts.enabled ?? true) {
ws.subscribe(opts.topics, opts.onBroadcast).catch(opts.onError)
return () => {
ws.unsubscribe(opts.topics, opts.onBroadcast).catch(opts.onError)
}
}
}
}, [opts.enabled, JSON.stringify(opts.topics)])
}
```
In `use-bets`, we have this hook to get live updates with useApiSubscription:
```ts
export const useContractBets = (
contractId: string,
opts?: APIParams<'bets'> & { enabled?: boolean }
) => {
const { enabled = true, ...apiOptions } = {
contractId,
...opts,
}
const optionsKey = JSON.stringify(apiOptions)
const [newBets, setNewBets] = usePersistentInMemoryState<Bet[]>(
[],
`${optionsKey}-bets`
)
const addBets = (bets: Bet[]) => {
setNewBets((currentBets) => {
const uniqueBets = sortBy(
uniqBy([...currentBets, ...bets], 'id'),
'createdTime'
)
return uniqueBets.filter((b) => !betShouldBeFiltered(b, apiOptions))
})
}
const isPageVisible = useIsPageVisible()
useEffect(() => {
if (isPageVisible && enabled) {
api('bets', apiOptions).then(addBets)
}
}, [optionsKey, enabled, isPageVisible])
useApiSubscription({
topics: [`contract/${contractId}/new-bet`],
onBroadcast: (msg) => {
addBets(msg.data.bets as Bet[])
},
enabled,
})
return newBets
}
```
---
Here are all the topics we broadcast, from `backend/shared/src/websockets/helpers.ts`
```ts
export function broadcastUpdatedPrivateUser(userId: string) {
// don't send private user info because it's private and anyone can listen
broadcast(`private-user/${userId}`, {})
}
export function broadcastUpdatedUser(user: Partial<User> & { id: string }) {
broadcast(`user/${user.id}`, { user })
}
export function broadcastUpdatedComment(comment: Comment) {
broadcast(`user/${comment.onUserId}/comment`, { comment })
}
```
---
We have our scripts in the directory `/backend/scripts`.
To write a script, run it inside the helper function called `runScript` that automatically fetches any secret keys and loads them into process.env.
Example from `/backend/scripts/manicode.ts`
```ts
import { runScript } from 'run-script'
runScript(async ({ pg }) => {
const userPrompt = process.argv[2]
await pg.none(...)
})
```
Generally scripts should be run by me, especially if they modify backend state or schema.
But if you need to run a script, you can use `bun`. For example:
```sh
bun run manicode.ts "Generate a page called cowp, which has cows that make noises!"
```
if that doesn't work, try
```sh
bun x ts-node manicode.ts "Generate a page called cowp, which has cows that make noises!"
```
---
Our backend is mostly a set of endpoints. We create new endpoints by adding to the schema in `/common/src/api/schema.ts`.
E.g. Here is a hypothetical bet schema:
```ts
bet: {
method: 'POST',
authed: true,
returns: {} as CandidateBet & { betId: string },
props: z
.object({
contractId: z.string(),
amount: z.number().gte(1),
replyToCommentId: z.string().optional(),
limitProb: z.number().gte(0.01).lte(0.99).optional(),
expiresAt: z.number().optional(),
// Used for binary and new multiple choice contracts (cpmm-multi-1).
outcome: z.enum(['YES', 'NO']).default('YES'),
//Multi
answerId: z.string().optional(),
dryRun: z.boolean().optional(),
})
.strict(),
}
```
Then, we define the bet endpoint in `backend/api/src/place-bet.ts`
```ts
export const placeBet: APIHandler<'bet'> = async (props, auth) => {
const isApi = auth.creds.kind === 'key'
return await betsQueue.enqueueFn(
() => placeBetMain(props, auth.uid, isApi),
[props.contractId, auth.uid]
)
}
```
And finally, you need to register the handler in `backend/api/src/routes.ts`
```ts
import { placeBet } from './place-bet'
...
const handlers = {
bet: placeBet,
...
}
```
---
We have two ways to access our postgres database.
```ts
import { db } from 'web/lib/supabase/db'
db.from('profiles').select('*').eq('user_id', userId)
```
and
```ts
import { createSupabaseDirectClient } from 'shared/supabase/init'
const pg = createSupabaseDirectClient()
pg.oneOrNone<Row<'profiles'>>('select * from profiles where user_id = $1', [userId])
```
The supabase client just uses the supabase client library, which is a wrapper around postgREST. It allows us to query and update the database directly from the frontend.
`createSupabaseDirectClient` is used on the backend. it lets us specify sql strings to run directly on our database, using the pg-promise library. The client (code in web) does not have permission to do this.
Another example using the direct client:
```ts
export const getUniqueBettorIds = async (
contractId: string,
pg: SupabaseDirectClient
) => {
const res = await pg.manyOrNone(
'select distinct user_id from contract_bets where contract_id = $1',
[contractId]
)
return res.map((r) => r.user_id as string)
}
```
(you may notice we write sql in lowercase)
We have a few helper functions for updating and inserting data into the database.
```ts
import {
buikInsert,
bulkUpdate,
bulkUpdateData,
bulkUpsert,
insert,
update,
updateData,
} from 'shared/supabase/utils'
...
const pg = createSupabaseDirectClient()
// you are encouraged to use tryCatch for these
const { data, error } = await tryCatch(
insert(pg, 'profiles', { user_id: auth.uid, ...body })
)
if (error) throw APIError(500, 'Error creating profile: ' + error.message)
await update(pg, 'profiles', 'user_id', { user_id: auth.uid, age: 99 })
await updateData(pg, 'private_users', { id: userId, notifications: { ... } })
```
The sqlBuilder from `shared/supabase/sql-builder.ts` can be used to construct SQL queries with re-useable parts. All it does is sanitize and output sql query strings. It has several helper functions including:
- `select`: Specifies the columns to select
- `from`: Specifies the table to query
- `where`: Adds WHERE clauses
- `orderBy`: Specifies the order of results
- `limit`: Limits the number of results
- `renderSql`: Combines all parts into a final SQL string
Example usage:
```typescript
const query = renderSql(
select('distinct user_id'),
from('contract_bets'),
where('contract_id = ${id}', { id }),
orderBy('created_time desc'),
limitValue != null && limit(limitValue)
)
const res = await pg.manyOrNone(query)
```
Use these functions instead of string concatenation.

View File

@@ -1,20 +1,15 @@
# Use the Prisma Postgres integration from Vercel Marketeplace to automatically connect a Prisma Postgres instance # You already have access to basic local functionality (UI, authentication, database read access).
# Or manually run `npx prisma init --db` to create a Prisma Postgres and manually set the `DATABASE_URL` below
# Create a random 32-character string or run `npx auth secret` to obtain one and set it as the `AUTH_SECRET` below # openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 -in backend/shared/src/googleApplicationCredentials-dev.json -out secrets/googleApplicationCredentials-dev.json.enc
GOOGLE_CREDENTIALS_ENC_PWD=nP7s3274uzOG4c2t
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
NEXTAUTH_SECRET=
NEXTAUTH_URL=http://localhost:3000
# Email configuration # Optional variables for full local functionality
EMAIL_SERVER_HOST=smtp.resend.dev
EMAIL_SERVER_PORT=587
EMAIL_SERVER_USER=BayesBond
EMAIL_SERVER_PASSWORD=
RESEND_API_KEY=
EMAIL_FROM=
# Development (SQLite) # For the location / distance filtering features.
DATABASE_URL=file:./dev.db # Create a free account at https://rapidapi.com/wirefreethought/api/geodb-cities and get an API key.
GEODB_API_KEY=
# For sending emails (e.g. for user sign up, password reset, notifications, etc.).
# Create a free account at https://resend.com and get an API key. Should start with "re_".
RESEND_KEY=

19
.env.test Normal file
View File

@@ -0,0 +1,19 @@
# Database
#DATABASE_URL=postgresql://test_user:test_password@localhost:5433/test_db
#DIRECT_URL=postgresql://test_user:test_password@localhost:5433/test_db
# Firebase
FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099
FIREBASE_STORAGE_EMULATOR_HOST=127.0.0.1:9199
FIRESTORE_EMULATOR_HOST=127.0.0.1:8080
# Next.js
NEXT_PUBLIC_API_URL=http://localhost:8088
NEXT_PUBLIC_FIREBASE_ENV=TEST
NEXT_PUBLIC_FIREBASE_EMULATOR=true
# API
NODE_ENV=test
PORT=8088
ENVIRONMENT=DEV

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: [CompassConnections] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: CompassMeet # Replace with a single Patreon username
open_collective: compass-connection # Replace with a single Open Collective username
ko_fi: compassconnections # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: CompassConnections # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

63
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Bug report
description: Create a report to help us improve
body:
- type: textarea
attributes:
label: Bug description
description: |
A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: |
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: |
A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Screenshots
description: |
If applicable, add screenshots to help explain your problem.
validations:
required: false
- type: textarea
attributes:
label: Info
description: |
- Browser: [e.g. chrome, safari]
- Device (if mobile): [e.g. iPhone6]
- Build info
placeholder: |
Build info from `Settings` -> `About`
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: |
Add any other context about the problem here.
validations:
required: false
- type: markdown
attributes:
value: |
Thanks for contributing!

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: true

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,40 @@
name: Feature request
description: Suggest an idea or improvement for this project
body:
- type: textarea
attributes:
label: Problem
description: |
A clear and concise description of what the problem is.
placeholder: I'm always frustrated when [...]
validations:
required: true
- type: textarea
attributes:
label: Solution
description: |
A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Alternatives
description: |
A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: textarea
attributes:
label: Additional context
description: |
Add any other context or screenshots about the feature request here.
validations:
required: false
- type: markdown
attributes:
value: |
Thanks for contributing!

25
.github/ISSUE_TEMPLATE/other.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Other
description: Use this only if no other issue type fits.
body:
- type: textarea
attributes:
label: Issue
description: |
A clear and concise description of the question or issue
validations:
required: true
- type: textarea
attributes:
label: Info
description: |
- Browser: [e.g. chrome, safari]
- Device (if mobile): [e.g. iPhone6]
- Build info
placeholder: |
Build info from `Settings` -> `About`
validations:
required: false
- type: markdown
attributes:
value: |
Thanks for contributing!

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,8 @@
<!-- Replace [ ] with [X] to check a box -->
- [ ] Closes #xxxx (Replace xxxx with the GitHub issue number, or delete line).
- [ ] Tests added and passed if fixing a bug or adding a new feature.
### Description
<!-- Describe your changes in detail -->

33
.github/actions/setup/action.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Setup
description: Checkout, cache and install dependencies
runs:
using: composite
steps:
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@v4
with:
path: |
node_modules
web/node_modules
backend/api/node_modules
backend/shared/node_modules
backend/email/node_modules
common/node_modules
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
shell: bash
- name: Post-install
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn postinstall
shell: bash

271
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,271 @@
---
trigger: always_on
description:
globs:
---
## Project Structure
Compass (compassmeet.com) is a transparent dating platform for forming deep, authentic 1-on-1 connections.
- **Next.js React frontend** `/web`
- Pages, components, hooks, lib
- **Express Node API server** `/backend/api`
- **Shared backend utilities** `/backend/shared`
- **Email functions** `/backend/email`
- **Database schema** `/backend/supabase`
- Supabase-generated types in `/backend/supabase/schema.ts`
- **Files shared between frontend and backend** `/common`
- Types (User, Profile, etc.) and utilities
- Try not to add package dependencies to common
- **Android app** `/android`
## Deployment
- Both dev and prod environments
- Backend on GCP (Google Cloud Platform)
- Frontend on Vercel
- Database on Supabase (PostgreSQL)
- Firebase for authentication and storage
## Code Guidelines
### Component Example
```tsx
import clsx from 'clsx'
import Link from 'next/link'
import {User} from 'common/user'
import {ProfileRow} from 'common/profiles/profile'
import {useUser} from 'web/hooks/use-user'
import {useT} from 'web/lib/locale'
interface ProfileCardProps {
user: User
profile: ProfileRow
}
export function ProfileCard({user, profile}: ProfileCardProps) {
const t = useT()
return (
<div className={clsx('bg-canvas-50 rounded-lg p-4')}>
<img src={user.avatarUrl} alt={user.name} />
<h3>{user.name}</h3>
<p>{profile.bio}</p>
</div>
)
}
```
We prefer many smaller components that each represent one logical unit, rather than one large component.
Export the main component at the top of the file. Name the component the same as the file (e.g., `profile-card.tsx`
`ProfileCard`).
### API Calls
**Server-side (getStaticProps):**
```typescript
import {api} from 'web/lib/api'
export async function getStaticProps() {
const profiles = await api('get-profiles', {})
return {
props: {profiles},
revalidate: 30 * 60, // 30 minutes
}
}
```
**Client-side - use hooks:**
```typescript
import {useAPIGetter} from 'web/hooks/use-api-getter'
function ProfileList() {
const {data, refresh} = useAPIGetter('get-profiles', {})
if (!data) return <CompassLoadingIndicator / >
return (
<div>
{
data.profiles.map((profile) => (
<ProfileCard key = {profile.id} user = {profile.user} profile = {profile}
/>
))
}
<button onClick = {refresh} > Refresh < /button>
< /div>
)
}
```
### Database Access
**Backend (pg-promise):**
```typescript
import {createSupabaseDirectClient} from 'shared/supabase/init'
const pg = createSupabaseDirectClient()
const user = await pg.oneOrNone<User>('SELECT * FROM users WHERE username = $1', [username])
```
**Frontend (Supabase client):**
```typescript
import {db} from 'web/lib/supabase/db'
const {data} = await db.from('profiles').select('*').eq('user_id', userId)
```
### Translation
```typescript
import {useT} from 'web/lib/locale'
function MyComponent() {
const t = useT()
return <h1>{t('welcome', 'Welcome to Compass')}</h1>
}
```
Translation files are in `common/messages/` (en.json, fr.json, de.json).
### Backend Endpoints
1. Define schema in `common/src/api/schema.ts`:
```typescript
'get-user-and-profile': {
method: 'GET',
authed: false,
rateLimited: true,
props: z.object({
username: z.string().min(1),
}),
returns: {} as {user: User; profile: ProfileRow | null},
summary: 'Get user and profile data by username',
tag: 'Users',
},
```
2. Create handler in `backend/api/src/`:
```typescript
import {APIError, APIHandler} from './helpers/endpoint'
export const getUserAndProfile: APIHandler<'get-user-and-profile'> = async ({username}, _auth) => {
const user = await getUserByUsername(username)
if (!user) {
throw APIErrors.notFound('User not found')
}
return {user, profile}
}
```
3. Register in `backend/api/src/app.ts`:
```typescript
import {getUserAndProfile} from './get-user-and-profile'
const handlers = {
'get-user-and-profile': getUserAndProfile,
// ...
}
```
### Profile Options (Interests, Causes, Work)
Options are stored in separate tables with many-to-many relationships:
- `interests`, `causes`, `work` - option values
- `profile_interests`, `profile_causes`, `profile_work` - junction tables
Fetch in parallel:
```typescript
const [interestsRes, causesRes, workRes] = await Promise.all([
db.from('profile_interests').select('interests(name, id)').eq('profile_id', profile.id),
db.from('profile_causes').select('causes(name, id)').eq('profile_id', profile.id),
db.from('profile_work').select('work(name, id)').eq('profile_id', profile.id),
])
```
## Testing
### Running Tests
```bash
# Jest (unit + integration)
yarn test
# Playwright (E2E)
yarn test:e2e
```
### Test Structure
- Unit tests: `*.unit.test.ts` in `tests/unit/`
- Integration tests: `*.integration.test.ts` in `tests/integration/`
- E2E tests: `*.e2e.spec.ts` in `tests/e2e/`
### Mocking Example
```typescript
jest.mock('shared/supabase/init')
import {createSupabaseDirectClient} from 'shared/supabase/init'
const mockPg = {
oneOrNone: jest.fn(),
tx: jest.fn(async (cb) => cb(mockTx)),
}
;(createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
```
## Important Patterns
### User Registration
- Create user + profile + options in single database transaction
- Return full profile data from creation API
- Don't use sleep() hacks - rely on transactional integrity
### API Errors
```typescript
import {APIError} from './helpers/endpoint'
throw APIErrors.notFound('User not found')
throw APIErrors.badRequest('Invalid input', {field: 'email'})
```
### Logging
- Use `debug()` from `common/logger` for development
- Use `log` from `shared/utils` for production
## Things to Avoid
- Don't use string concatenation for SQL queries
- Don't add sleep() delays for "eventual consistency"
- Don't create separate API calls when data can be batched in one transaction
- Don't use console.log - use `debug()` or `log()`
## Key Dependencies
- Node.js 20+
- React 19
- Next.js 16
- Supabase (PostgreSQL)
- Firebase (Auth, Storage)
- Tailwind CSS
- Jest (testing)
- Playwright (E2E testing)

View File

@@ -1,31 +0,0 @@
name: Check Next.js
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22' # or match the version in .nvmrc or package.json
- name: Install dependencies
run: npm ci
- name: Build the app
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run build

View File

@@ -0,0 +1,67 @@
name: CD Android Live Update
on:
push:
branches: [main, master]
paths:
- 'android/capawesome.json'
- '.github/workflows/cd-android-live-update.yml'
jobs:
check-version:
name: Check Version
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.check.outputs.changed }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Read current version
id: current
run: |
current=$(jq -r '.version' android/capawesome.json)
echo "version=$current" >> $GITHUB_OUTPUT
- name: Read previous version
id: previous
run: |
if git show HEAD^:android/capawesome.json >/dev/null 2>&1; then
previous=$(git show HEAD^:android/capawesome.json | jq -r '.version')
else
previous="none"
fi
echo "version=$previous" >> $GITHUB_OUTPUT
- name: Compare versions
id: check
run: |
echo "current=${{ steps.current.outputs.version }}"
echo "previous=${{ steps.previous.outputs.version }}"
if [ "${{ steps.current.outputs.version }}" = "${{ steps.previous.outputs.version }}" ]; then
echo "Version unchanged. Skipping deploy."
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: check-version
if: needs.check-version.outputs.changed == 'true'
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: Deploy Live Update
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_SUPABASE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_KEY }}
CAPAWESOME_TOKEN: ${{ secrets.CAPAWESOME_TOKEN }}
commitRef: ${{ github.head_ref || github.ref_name }}
commitSha: ${{ github.sha }}
run: yarn android-live-update

88
.github/workflows/cd-android.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: Android Release
on:
push:
branches:
- main
paths:
- 'android/app/build.gradle'
- '.github/workflows/cd-android.yml'
jobs:
check-version:
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.version_check.outputs.should_build }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Get previous versionCode
id: prev_version
run: |
git checkout HEAD^
PREV=$(grep versionCode android/app/build.gradle | awk '{print $2}')
echo "prev_version=$PREV" >> $GITHUB_OUTPUT
git checkout -
- name: Get current versionCode
id: curr_version
run: |
CURR=$(grep versionCode android/app/build.gradle | awk '{print $2}')
echo "curr_version=$CURR" >> $GITHUB_OUTPUT
- name: Compare versionCodes
id: version_check
run: |
if [ "${{ steps.curr_version.outputs.curr_version }}" -gt "${{ steps.prev_version.outputs.prev_version }}" ]; then
echo "should_build=true" >> $GITHUB_OUTPUT
else
echo "versionCode not increased. Skipping build."
echo "should_build=false" >> $GITHUB_OUTPUT
fi
build-and-release:
runs-on: ubuntu-latest
needs: check-version
if: needs.check-version.outputs.should_build == 'true'
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
cache: gradle
- name: Compile Web App into Android assets
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_SUPABASE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_KEY }}
run: yarn build-sync-android
- name: Build AAB
run: |
cd android
echo "${{ secrets.ANDROID_GOOGLE_SERVICES_JSON }}" | base64 -d > app/google-services.json
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore
cp release.keystore app/release.keystore
chmod +x gradlew
./gradlew bundleRelease \
-Pandroid.injected.signing.store.file=release.keystore \
-Pandroid.injected.signing.store.password=${{ secrets.ANDROID_KEYSTORE_PASSWORD }} \
-Pandroid.injected.signing.key.alias=compass \
-Pandroid.injected.signing.key.password=${{ secrets.ANDROID_KEY_PASSWORD }}
- name: Upload to Play Store (Internal Track)
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: com.compassconnections.app
releaseFiles: android/app/build/outputs/bundle/release/app-release.aab
track: internal

87
.github/workflows/cd-api.yml vendored Normal file
View File

@@ -0,0 +1,87 @@
name: API Release
on:
push:
branches: [main, master]
paths:
- 'backend/api/package.json'
- '.github/workflows/cd-api.yml'
jobs:
check-version:
name: Check Version
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.check.outputs.changed }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Read current version
id: current
run: |
current=$(jq -r '.version' backend/api/package.json)
echo "version=$current" >> $GITHUB_OUTPUT
- name: Read previous version
id: previous
run: |
if git show HEAD^:backend/api/package.json >/dev/null 2>&1; then
previous=$(git show HEAD^:backend/api/package.json | jq -r '.version')
else
previous="none"
fi
echo "version=$previous" >> $GITHUB_OUTPUT
- name: Compare versions
id: check
run: |
echo "current=${{ steps.current.outputs.version }}"
echo "previous=${{ steps.previous.outputs.version }}"
if [ "${{ steps.current.outputs.version }}" = "${{ steps.previous.outputs.version }}" ]; then
echo "Version unchanged. Skipping deploy."
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: check-version
if: needs.check-version.outputs.changed == 'true'
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Install gcloud CLI
uses: google-github-actions/setup-gcloud@v2
with:
project_id: compass-130ba
- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker us-west1-docker.pkg.dev --quiet
- name: Install Tofu (Terraform)
run: |
LATEST=https://github.com/opentofu/opentofu/releases/download/v1.10.5/tofu_1.10.5_linux_amd64.zip
curl -LO "$LATEST"
unzip -o tofu_*_linux_amd64.zip
sudo mv tofu /usr/local/bin/
rm tofu_*_linux_amd64.zip
echo "OpenTofu version: $(tofu version)"
cd backend/api
tofu init
- name: Run deploy script
run: |
chmod +x backend/api/deploy-api.sh
backend/api/deploy-api.sh

34
.github/workflows/cd.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: GitHub Release
# Must select "Read and write permissions" in GitHub → Repo → Settings → Actions → General → Workflow permissions
on:
push:
branches: [main, master]
paths:
- 'package.json'
- '.github/workflows/cd.yml'
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@master
with:
fetch-depth: 0 # To fetch all history for tags
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Tag and release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
./scripts/release.sh

68
.github/workflows/ci-e2e.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
e2e:
name: E2E Tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: Cache Firebase emulators
uses: actions/cache@v4
with:
path: ~/.cache/firebase/emulators
key: firebase-emulators-${{ hashFiles('firebase.json') }}
restore-keys: firebase-emulators-
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('package.json') }}
- name: Install Java (for Firebase emulators)
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21' # Required for firebase-tools@15+
- name: Install Playwright browsers
run: npx playwright install chromium --with-deps
# Docker load from cache is actually slower than pulling the images every time with supabase start
- name: Start Supabase
run: ./scripts/supabase_start.sh
- name: Run E2E tests
env:
SKIP_DB_CLEANUP: true # Don't try to stop Docker in CI
FIREBASE_TOKEN: 'dummy' # Suppresses auth warning
# or
run: |
yarn test:e2e
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: tests/reports/playwright-report/
retention-days: 7
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results/
retention-days: 7
if-no-files-found: ignore

53
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
name: Lint & Typecheck
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: Lint
run: yarn lint
- name: Typecheck
run: yarn typecheck
test:
name: Jest Tests
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: Run Jest tests
env:
NEXT_PUBLIC_FIREBASE_ENV: DEV
run: |
yarn test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
if: success()
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: |
backend/api/coverage/lcov.info
backend/shared/coverage/lcov.info
backend/email/coverage/lcov.info
common/coverage/lcov.info
web/coverage/lcov.info
flags: unit
fail_ci_if_error: true
slug: CompassConnections/Compass

59
.gitignore vendored
View File

@@ -13,6 +13,10 @@
# testing # testing
/coverage /coverage
# Playwright
/tests/reports/playwright-report
/tests/e2e/web/.auth/
# next.js # next.js
/.next/ /.next/
/out/ /out/
@@ -33,6 +37,8 @@ yarn-error.log*
# env files (can opt-in for committing if needed) # env files (can opt-in for committing if needed)
.env .env
.env.local .env.local
.env.*
.envrc
# vercel # vercel
.vercel .vercel
@@ -41,9 +47,60 @@ yarn-error.log*
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
.idea/
node_modules
yarn-error.log
dev
firebase-debug.log
tsconfig.tsbuildinfo
*.db *.db
*prisma/migrations *prisma/migrations
martin martin
email-preview
.obsidian .obsidian
.idea .idea
*.last-run.json
*lock.hcl
/web/pages/_test.tsx
*.png
*.jpg
*.jpeg
*.gif
*.svg
*.ico
*.mp4
*.mov
*.webp
*.avi
*.wmv
*.mp3
*.wav
*.flac
*.aac
*.zip
*.tar.gz
*.rar
/favicon_color.ico
/backend/shared/src/googleApplicationCredentials-dev.json
*.tfstate
*.tfstate.backup
*.terraform
/backups/firebase/auth/data/
/backups/firebase/storage/data/
android/app/release*
icons/
*.bak
test-results
/.nyc_output/
**/coverage
*my-release-key.keystore
.vscode/settings.json

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
npx lint-staged

1294
.junie/guidelines.md Normal file
View File

File diff suppressed because it is too large Load Diff

28
.lintstagedrc.mjs Normal file
View File

@@ -0,0 +1,28 @@
export default {
'web/**/*.{ts,tsx,js,jsx}': (files) => [
`prettier --write ${files.join(' ')}`,
`eslint --config web/eslint.config.mjs --fix ${files.join(' ')}`,
`eslint --config web/eslint.config.mjs --max-warnings 0 ${files.join(' ')}`,
],
'common/**/*.{ts,tsx,js,jsx}': (files) => [
`prettier --write ${files.join(' ')}`,
`eslint --config common/eslint.config.mjs --fix ${files.join(' ')}`,
`eslint --config common/eslint.config.mjs --max-warnings 0 ${files.join(' ')}`,
],
'backend/api/**/*.{ts,tsx,js,jsx}': (files) => [
`prettier --write ${files.join(' ')}`,
`eslint --config backend/api/eslint.config.mjs --fix ${files.join(' ')}`,
`eslint --config backend/api/eslint.config.mjs --max-warnings 0 ${files.join(' ')}`,
],
'backend/shared/**/*.{ts,tsx,js,jsx}': (files) => [
`prettier --write ${files.join(' ')}`,
`eslint --config backend/shared/eslint.config.mjs --fix ${files.join(' ')}`,
`eslint --config backend/shared/eslint.config.mjs --max-warnings 0 ${files.join(' ')}`,
],
'backend/email/**/*.{ts,tsx,js,jsx}': (files) => [
`prettier --write ${files.join(' ')}`,
`eslint --config backend/email/eslint.config.mjs --fix ${files.join(' ')}`,
`eslint --config backend/email/eslint.config.mjs --max-warnings 0 ${files.join(' ')}`,
],
'**/*.{json,css,scss,md}': (files) => [`prettier --write ${files.join(' ')}`],
}

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
loglevel=error

35
.prettierignore Normal file
View File

@@ -0,0 +1,35 @@
# Dependencies
node_modules
.yarn
# Build outputs
dist
build
.next
out
lib
# Generated files
coverage
*.min.js
*.min.css
# Database / migrations
**/*.sql
# Config / lock files
yarn.lock
package-lock.json
pnpm-lock.yaml
# Android / iOS
android
ios
capacitor.config.ts
# Playwright
tests/reports
playwright-report
coverage
.vscode

27
.prettierrc Normal file
View File

@@ -0,0 +1,27 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"singleAttributePerLine": false,
"bracketSpacing": false,
"printWidth": 100,
"trailingComma": "all",
"plugins": ["prettier-plugin-sql", "prettier-plugin-packagejson"],
"overrides": [
{
"files": "*.sql",
"options": {
"language": "postgresql",
"keywordCase": "lower",
"logicalOperatorNewline": "before"
}
},
{
"files": "*.svg",
"options": {
"parser": "html"
}
}
]
}

33
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,33 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Current Test",
"type": "node",
"request": "launch",
"runtimeArgs": [
"--inspect-brk",
"${workspaceRoot}/node_modules/.bin/jest",
"${fileBasename}",
"--runInBand"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
// {
// "type": "node",
// "request": "launch",
// "name": "Launch Program",
// "skipFiles": [
// "<node_internals>/**"
// ],
// "program": "${workspaceFolder}/backend/api/tests/unit/get-profiles.unit.test.ts",
// "outFiles": [
// "${workspaceFolder}/**/*.js"
// ]
// }
]
}

295
.windsurf/rules/compass.md Normal file
View File

@@ -0,0 +1,295 @@
---
trigger: always_on
description:
globs:
---
## Project Structure
Compass (compassmeet.com) is a transparent dating platform for forming deep, authentic 1-on-1 connections.
- **Next.js React frontend** `/web`
- Pages, components, hooks, lib
- **Express Node API server** `/backend/api`
- **Shared backend utilities** `/backend/shared`
- **Email functions** `/backend/email`
- **Database schema** `/backend/supabase`
- Supabase-generated types in `/backend/supabase/schema.ts`
- **Files shared between frontend and backend** `/common`
- Types (User, Profile, etc.) and utilities
- Try not to add package dependencies to common
- **Android app** `/android`
## Deployment
- Both dev and prod environments
- Backend on GCP (Google Cloud Platform)
- Frontend on Vercel
- Database on Supabase (PostgreSQL)
- Firebase for authentication and storage
## Code Guidelines
### Component Example
```tsx
import clsx from 'clsx'
import Link from 'next/link'
import {User} from 'common/user'
import {ProfileRow} from 'common/profiles/profile'
import {useUser} from 'web/hooks/use-user'
import {useT} from 'web/lib/locale'
interface ProfileCardProps {
user: User
profile: ProfileRow
}
export function ProfileCard({user, profile}: ProfileCardProps) {
const t = useT()
return (
<div className={clsx('bg-canvas-50 rounded-lg p-4')}>
<img src={user.avatarUrl} alt={user.name} />
<h3>{user.name}</h3>
<p>{profile.bio}</p>
</div>
)
}
```
We prefer many smaller components that each represent one logical unit, rather than one large component.
Export the main component at the top of the file. Name the component the same as the file (e.g., `profile-card.tsx`
`ProfileCard`).
### API Calls
**Server-side (getStaticProps):**
```typescript
import {api} from 'web/lib/api'
export async function getStaticProps() {
const profiles = await api('get-profiles', {})
return {
props: {profiles},
revalidate: 30 * 60, // 30 minutes
}
}
```
**Client-side - use hooks:**
```typescript
import {useAPIGetter} from 'web/hooks/use-api-getter'
function ProfileList() {
const {data, refresh} = useAPIGetter('get-profiles', {})
if (!data) return <Loading / >
return (
<div>
{
data.profiles.map((profile) => (
<ProfileCard key = {profile.id} user = {profile.user} profile = {profile}
/>
))
}
<button onClick = {refresh} > Refresh < /button>
< /div>
)
}
```
### Database Access
**Backend (pg-promise):**
```typescript
import {createSupabaseDirectClient} from 'shared/supabase/init'
const pg = createSupabaseDirectClient()
const user = await pg.oneOrNone<User>('SELECT * FROM users WHERE username = $1', [username])
```
**Frontend (Supabase client):**
```typescript
import {db} from 'web/lib/supabase/db'
const {data} = await db.from('profiles').select('*').eq('user_id', userId)
```
### Translation
```typescript
import {useT} from 'web/lib/locale'
function MyComponent() {
const t = useT()
return <h1>{t('welcome', 'Welcome to Compass'
)
}
</h1>
}
```
Translation files are in `common/messages/` (en.json, fr.json, de.json).
### Backend Endpoints
1. Define schema in `common/src/api/schema.ts`:
```typescript
'get-user-and-profile'
:
{
method: 'GET',
authed
:
false,
rateLimited
:
true,
props
:
z.object({
username: z.string().min(1),
}),
returns
:
{
}
as
{
user: User;
profile: ProfileRow | null
}
,
summary: 'Get user and profile data by username',
tag
:
'Users',
}
,
```
2. Create handler in `backend/api/src/`:
```typescript
import {APIError, APIHandler} from './helpers/endpoint'
export const getUserAndProfile: APIHandler<'get-user-and-profile'> = async ({username}, _auth) => {
const user = await getUserByUsername(username)
if (!user) {
throw APIErrors.notFound('User not found')
}
return {user, profile}
}
```
3. Register in `backend/api/src/app.ts`:
```typescript
import {getUserAndProfile} from './get-user-and-profile'
const handlers = {
'get-user-and-profile': getUserAndProfile,
// ...
}
```
### Profile Options (Interests, Causes, Work)
Options are stored in separate tables with many-to-many relationships:
- `interests`, `causes`, `work` - option values
- `profile_interests`, `profile_causes`, `profile_work` - junction tables
Fetch in parallel:
```typescript
const [interestsRes, causesRes, workRes] = await Promise.all([
db.from('profile_interests').select('interests(name, id)').eq('profile_id', profile.id),
db.from('profile_causes').select('causes(name, id)').eq('profile_id', profile.id),
db.from('profile_work').select('work(name, id)').eq('profile_id', profile.id),
])
```
### API Errors
```typescript
import {APIError} from './helpers/endpoint'
throw APIErrors.notFound('User not found')
throw APIErrors.badRequest('Invalid input', {field: 'email'})
```
### Logging
- Use `debug()` from `common/logger` for development
- Use `log` from `shared/utils` for production
## Testing
### Running Tests
```bash
# Jest (unit + integration)
yarn test
# Playwright (E2E)
yarn test:e2e
```
### Test Structure
- Unit tests: `*.unit.test.ts` in `tests/unit/`
- Integration tests: `*.integration.test.ts` in `tests/integration/`
- E2E tests: `*.e2e.spec.ts` in `tests/e2e/`
### Mocking Example
```typescript
jest.mock('shared/supabase/init')
import {createSupabaseDirectClient} from 'shared/supabase/init'
const mockPg = {
oneOrNone: jest.fn(),
tx: jest.fn(async (cb) => cb(mockTx)),
}
;(createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
```
## Important Patterns
### User Registration
- Create user + profile + options in single database transaction
- Return full profile data from creation API
- Don't use sleep() hacks - rely on transactional integrity
## Things to Avoid
- Don't use string concatenation for SQL queries
- Don't add sleep() delays for "eventual consistency"
- Don't create separate API calls when data can be batched in one transaction
- Don't use console.log - use `debug()` or `log()`
- Don't remove commented code
## Key Dependencies
- Node.js 20+
- React 19
- Next.js 16
- Supabase (PostgreSQL)
- Firebase (Auth, Storage)
- Tailwind CSS
- Jest (testing)
- Playwright (E2E testing)

42
.windsurf/rules/next.md Normal file
View File

@@ -0,0 +1,42 @@
---
trigger: manual
description:
globs:
---
### Translations
```typescript
import {useT} from 'web/lib/locale'
const t = useT()
t('common.key', 'English translations')
```
Translations should go to the JSON files in `web/messages` (`de.json` and `fr.json`, as of now).
### Misc coding tips
We have many useful hooks that should be reused rather than rewriting them again.
---
We prefer using lodash functions instead of reimplementing them with for loops:
```ts
import {keyBy, uniq} from 'lodash'
const betsByUserId = keyBy(bets, 'userId')
const betIds = uniq(bets, (b) => b.id)
```
---
Instead of Sets, consider using lodash's uniq function:
```ts
const betIds = uniq([])
for (const id of betIds) {
...
}
```

View File

@@ -0,0 +1,288 @@
---
description: Adding Translations to an Existing File (using useT)
---
## AI Assistant Workflow — Adding Translations to an Existing File (using `useT`)
This is **not** about adding a new language.
This is about correctly adding new translation keys to a feature or component that already exists.
Follow this strictly.
---
### 1⃣ Identify All User-Facing Strings
Scan the file and list every:
- Button label
- Title
- Placeholder
- Tooltip
- Toast message
- Modal text
- Validation error
- SEO metadata
- Empty state message
If a string is visible to users, it must be translated.
Do **not** leave inline English in JSX.
Bad:
```tsx
<button>Delete account</button>
```
Correct:
```tsx
<button>{t('settings.delete_account', 'Delete account')}</button>
```
---
### 2⃣ Import and Initialize `useT`
At the top of the file:
```tsx
import {useT} from 'web/lib/locale'
```
Inside the component:
```tsx
const t = useT()
```
No exceptions.
Do not manually access locale files.
For the backend, use
```tsx
import {createT} from 'shared/locale'
const t = createT(locale)
```
---
### 3⃣ Replace Hardcoded Strings
Wrap every string in:
```tsx
t('namespace.key', 'Default English text')
```
Example:
```tsx
t('news.seo.description_general', 'All news and code updates for Compass')
```
Rules:
- First argument = stable translation key
- Second argument = default English fallback
- The English text must exactly match what you want displayed
---
### 4⃣ Naming Convention for Keys
Use structured namespaces.
Good:
```
profile.delete.confirm_title
profile.delete.confirm_body
settings.notifications.email_label
events.create.submit_button
```
Bad:
```
delete1
buttonText
labelNew
```
Keys must be:
- Hierarchical
- Feature-scoped
- Predictable
- Stable (never rename casually)
---
### 5⃣ Add the Keys to All Existing Locale Files
After updating the component:
1. Open:
```
common/src/messages/fr.json
common/src/messages/de.json
...
```
2. Add the new keys to **every language file**
Keys must be identical across all files.
Example:
```json
{
"confirm_title": "Delete account?",
"confirm_body": "This action cannot be undone."
}
```
Then translate values for non-English files.
---
### 6⃣ Use an LLM for Draft Translation (Correctly)
When translating large additions:
- Copy only the new JSON section
- Ask:
```
Translate the values of the JSON below to French.
Keep all keys unchanged.
Return valid JSON only.
```
Never let the model modify keys.
Then manually review.
LLMs make mistakes:
- Wrong tone
- Cultural mismatch
- Broken JSON
- Overly long mobile labels
You must verify.
---
### 7⃣ Respect Mobile Constraints
Certain keys must stay short (< 10 characters):
```
nav.home
nav.messages
nav.more
nav.notifs
nav.people
```
If you add navigation items, enforce brevity.
---
### 8⃣ Handle Variables Properly
For dynamic values:
```tsx
t('events.count', '{count} events', {count})
```
Make sure placeholders match across all languages.
Do not concatenate strings manually.
Bad:
```tsx
'Events: ' + count
```
Correct:
```tsx
t('events.count', '{count} events', {count})
```
---
### 9⃣ SEO & Metadata
Even SEO descriptions must use translations:
```tsx
<meta
name="description"
content={t('profile.seo.description', 'View user profiles and connect with like-minded people.')}
/>
```
Do not hardcode metadata.
---
### 10⃣ Final Verification Checklist
Before committing:
- No visible English strings left
- All new keys added to all locale files
- No missing translations warnings
- JSON is valid
- Mobile nav labels are short
- Variables work in all languages
- No duplicate keys
- Namespaces are consistent
---
### Example — Full Pattern
```tsx
import {useT} from 'web/lib/locale'
export default function DeleteModal() {
const t = useT()
return (
<>
<h2>{t('profile.delete.confirm_title', 'Delete account?')}</h2>
<p>{t('profile.delete.confirm_body', 'This action cannot be undone.')}</p>
<button>{t('profile.delete.confirm_button', 'Delete')}</button>
</>
)
}
```
---
### Summary
When adding translations to a file:
1. Replace every user-visible string
2. Use `useT()`
3. Create structured keys
4. Add keys to every locale file
5. Translate values carefully
6. Validate JSON
7. Test UI in multiple languages
If you skip any of these steps, you create future maintenance debt.
There is no shortcut.

2
.yarnrc Normal file
View File

@@ -0,0 +1,2 @@
save-exact true
ignore-engines true

114
AGENTS.md Normal file
View File

@@ -0,0 +1,114 @@
# AGENTS.md - AI Assistant Guidelines for Compass
This file provides guidance for AI assistants working on the Compass codebase.
## Project Overview
Compass (compassmeet.com) is a transparent dating platform for forming deep, authentic 1-on-1 connections. Built with Next.js, React, Supabase, Firebase, and Google Cloud.
## Project Structure
```
/web # Next.js frontend (React, Tailwind CSS)
/backend/api # Express.js REST API
/backend/shared # Shared backend utilities
/backend/email # Email functions
/common # Shared types and utilities between frontend/backend
/supabase # Database schema and migrations
/android # Android mobile app
```
## Key Conventions
### Database Access
- Use `createSupabaseDirectClient()` for backend SQL queries (pg-promise)
- Use Supabase JS client (`db.from('table')`) for frontend queries
- Never use string concatenation for SQL - use parameterized queries
### API Development
1. Add endpoint schema to `common/src/api/schema.ts`
2. Create handler in `backend/api/src/`
3. Register in `backend/api/src/app.ts`
### Component Patterns
- Export main component at top of file
- Name component same as file (e.g., `profile-card.tsx``ProfileCard`)
- Use smaller, composable components over large ones
### Internationalization
- Translation files in `common/messages/` (en.json, fr.json, de.json)
- Use `useT()` hook: `t('key', 'fallback')`
### Testing
- Unit tests: `*.unit.test.ts` in package `tests/unit/`
- Mock external dependencies (DB, APIs, time)
- Use `jest.mock()` at top of test files
### Profile System
- Profile fields stored in `profiles` table
- Options (interests, causes, work) stored in separate tables with many-to-many relationship
- Always fetch profile options in parallel using Promise.all
### User Registration Flow
1. Create user + profile + options in single transaction
2. Never use sleep() hacks - rely on transactional integrity
3. Return full profile data from creation API
### Important Patterns
#### Frontend API calls (server-side):
```typescript
const result = await api('endpoint-name', {props})
```
#### Frontend API calls (client-side):
```typescript
const {data} = useAPIGetter('endpoint-name', {props})
```
#### Translation:
```typescript
const t = useT()
return <div>{t('key', 'Default text')}</div>
```
## Common Tasks
### Adding a profile field
1. Add column to `profiles` table via migration
2. Add to schema in `common/src/api/schema.ts`
3. Update frontend forms/components
### Adding translations
1. Add key to `common/messages/en.json`
2. Add translations to `fr.json`, `de.json`, etc.
## Things to Avoid
- Don't use string concatenation for SQL queries
- Don't add sleep() delays for "eventual consistency" - fix at DB level
- Don't create separate API calls when data can be batched in one transaction
- Don't use console.log - use `debug()` from `common/logger`
## Key Dependencies
- Node.js 20+
- React 19
- Next.js 16
- Supabase (PostgreSQL)
- Firebase (Auth, Storage)
- Tailwind CSS
- Jest (testing)
- Playwright (E2E testing)

View File

@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the - Focusing on what is best not just for us as individuals, but for the
overall community overall community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or - The use of sexualized language or imagery, and sexual attention or
advances of any kind advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email - Publishing others' private information, such as a physical or email
address, without their explicit permission address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban ### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community **Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals. individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within **Consequence**: A permanent ban from any sort of public interaction within

View File

@@ -1,125 +1,493 @@
# Contributing to This Repository # Contributing to Compass
We welcome pull requests, but only if they meet the project's quality and design standards. Follow the process below precisely to avoid wasting time—yours or ours. Thank you for your interest in contributing to Compass! This document provides comprehensive guidelines for contributing
to this open-source project.
## Prerequisites ## Table of Contents
- Familiarity with Git and GitHub (basic commands, branching, forking, etc.) - [Code of Conduct](#code-of-conduct)
- A functioning development environment - [Getting Started](#getting-started)
- Node.js, Python, or other relevant runtime/tools installed (check the `README.md`) - [Development Environment](#development-environment)
- [Project Structure](#project-structure)
- [Coding Standards](#coding-standards)
- [Making Changes](#making-changes)
- [Testing](#testing)
- [Pull Request Guidelines](#pull-request-guidelines)
- [Commit Message Guidelines](#commit-message-guidelines)
- [Documentation](#documentation)
- [Questions and Support](#questions-and-support)
## Fork & Clone ## Code of Conduct
1. **Fork the repository** using the GitHub UI. Please read and follow our [Code of Conduct](./CODE_OF_CONDUCT.md). We are committed to providing a welcoming and
2. **Clone your fork** locally: inclusive environment for all contributors.
## Getting Started
### Prerequisites
Before contributing, ensure you have the following installed:
- **Node.js** 20.x or later
- **Yarn** 1.x (classic)
- **Git**
- **Docker** (optional, for isolated development)
### Fork and Clone
1. Fork the [repository](https://github.com/CompassConnections/Compass) on GitHub
2. Clone your fork:
```bash ```bash
git clone https://github.com/your-username/BayesBond.git git clone https://github.com/<your-username>/Compass.git
cd your-fork cd Compass
```
3. **Add the upstream remote**: 3. Add the upstream remote:
```bash ```bash
git remote add upstream https://github.com/BayesBond/BayesBond.git git remote add upstream https://github.com/CompassConnections/Compass.git
``` ```
## Create a New Branch ### Install Dependencies
Never work on `main` or `master`.
```bash ```bash
git checkout -b fix/brief-but-specific-description yarn install --frozen-lockfile
``` ```
Use a clear, descriptive branch name. Avoid vague names like `patch-1`. ## Development Environment
## Stay Updated ### Running the Development Server
Before you start, make sure your fork is up to date:
```bash ```bash
git fetch upstream yarn dev
git checkout main
git merge upstream/main
``` ```
Then rebase your feature branch if needed: Visit http://localhost:3000 to see the application.
### Isolated Development (Recommended)
For full isolation with local Supabase and Firebase emulators:
```bash ```bash
git checkout fix/your-feature yarn dev:isolated
git rebase main
``` ```
## Make Atomic Commits Benefits:
Each commit should represent a single logical change. Follow this format: - No conflicts with other contributors
- Works offline
- Faster database queries
- Free to reset and reseed data
```text Requirements:
type(scope): concise description
body explaining what and why, if necessary - Docker (~500MB)
- Supabase CLI
- Java 21+ (for Firebase emulators)
- Firebase CLI
See the [README](./README.md) for detailed setup instructions.
### Running Tests
```bash
# Run all tests
yarn test
# Run tests with coverage
yarn test:coverage
# Run tests in watch mode
yarn test:watch
# Run E2E tests
yarn test:e2e
``` ```
### Linting and Type Checking
```bash
# Lint all packages
yarn lint
# Fix linting issues
yarn lint-fix
# Type check all packages
yarn typecheck
```
## Project Structure
This is a Yarn workspaces monorepo with the following packages:
```
Compass/
├── web/ # Next.js web application
│ ├── components/ # React components
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utilities and services
│ ├── pages/ # Next.js pages
│ └── messages/ # Internationalization files
├── backend/
│ ├── api/ # Express API server
│ ├── shared/ # Shared backend utilities
│ ├── email/ # React email templates
│ └── scripts/ # Database migration scripts
├── common/ # Shared TypeScript types and utilities
├── supabase/ # Database migrations and config
├── android/ # Capacitor Android app
└── docs/ # Project documentation
```
### Key Technologies
| Layer | Technology |
| -------- | -------------------------------- |
| Frontend | Next.js 16, React 19, TypeScript |
| Styling | Tailwind CSS |
| Backend | Express.js, Node.js |
| Database | PostgreSQL (Supabase) |
| Auth | Firebase Auth |
| Storage | Firebase Storage |
| Mobile | Capacitor (Android) |
| Testing | Jest, Playwright |
## Coding Standards
### TypeScript
- Use strict TypeScript typing
- Avoid `any` type; use `unknown` when necessary
- Prefer interfaces over types for object shapes
- Use `const` assertions where appropriate
### React Components
- Use functional components with hooks
- Name components after their file name
- Export primary component at the top of the file
- Use composition over inheritance
- Keep components small and focused
Example component structure:
```tsx
import clsx from 'clsx'
import {useState} from 'react'
interface ProfileCardProps {
name: string
age: number
onSelect?: (id: string) => void
className?: string
}
export function ProfileCard({name, age, onSelect, className}: ProfileCardProps) {
const [selected, setSelected] = useState(false)
const handleClick = () => {
setSelected(!selected)
onSelect?.(name)
}
return (
<div className={clsx('card', selected && 'selected', className)}>
<h3>
{name}, {age}
</h3>
<button onClick={handleClick}>Select</button>
</div>
)
}
```
### Naming Conventions
- **Files**: kebab-case (`profile-card.tsx`)
- **Components**: PascalCase (`ProfileCard`)
- **Hooks**: camelCase with `use` prefix (`useUserProfile`)
- **Constants**: SCREAMING_SNAKE_CASE
- **Types/Interfaces**: PascalCase
### Import Order
Run `yarn lint-fix` to automatically sort imports:
1. External libraries (React, Next.js, etc.)
2. Internal packages (`common/`, `shared/`)
3. Relative imports (`../`, `./`)
4. Type imports
### Error Handling
- Use try-catch for async operations
- Create custom error types for API errors
- Implement error boundaries for React components
- Log errors with appropriate context
Example: Example:
```typescript
import {APIError} from './errors'
try {
const result = await api('endpoint', params)
return result
} catch (err) {
if (err instanceof APIError) {
logger.error('API error', {status: err.status, message: err.message})
} else {
logger.error('Unexpected error', err)
}
throw err
}
```
### Accessibility
- Use semantic HTML elements
- Include ARIA labels where appropriate
- Ensure keyboard navigation works
- Use the `SkipLink` component for main content
- Announce dynamic content changes with `useLiveRegion`
```tsx
import {useLiveRegion} from 'web/components/live-region'
function MyComponent() {
const {announce} = useLiveRegion()
const handleAction = () => {
// Action completed
announce('Action successful', 'polite')
}
}
```
## Making Changes
### Creating a Branch
Never work directly on `main`. Create a new branch:
```bash
git checkout -b type/short-description
```
Branch types:
- `feat/` - New features
- `fix/` - Bug fixes
- `docs/` - Documentation
- `refactor/` - Code refactoring
- `test/` - Adding/updating tests
- `chore/` - Maintenance tasks
### Making Commits
Keep commits atomic and descriptive:
```bash
git add .
git commit -m "feat(profiles): add compatibility score display
- Added compatibility score calculation
- Display score on profile cards
- Added tests for scoring algorithm"
```
See [Commit Message Guidelines](#commit-message-guidelines) for details.
### Keeping Your Fork Updated
```bash
# Fetch latest from upstream
git fetch upstream
# Update main branch
git checkout main
git merge upstream/main
# Rebase your feature branch
git checkout feat/your-feature
git rebase main
```
## Testing
### Writing Tests
#### Unit Tests
Place tests in `tests/unit/` within each package:
```typescript
// web/tests/unit/my-function.test.ts
import {myFunction} from '../my-function'
describe('myFunction', () => {
it('should return correct output', () => {
expect(myFunction('input')).toBe('expected')
})
})
```
#### Integration Tests
Place in `tests/integration/`:
```typescript
// web/tests/integration/api.test.ts
import {render, screen} from '@testing-library/react'
import {MyComponent} from '../MyComponent'
describe('MyComponent', () => {
it('renders correctly', () => {
render(<MyComponent / >)
expect(screen.getByText('Hello')).toBeInTheDocument()
})
})
```
#### E2E Tests
Place in `tests/e2e/` at the root:
```typescript
// tests/e2e/web/specs/onboarding.spec.ts
import {test, expect} from '@playwright/test'
test('onboarding flow', async ({page}) => {
await page.goto('/signup')
await page.fill('[name="email"]', 'test@example.com')
await page.click('button[type="submit"]')
await expect(page).toHaveURL('/onboarding')
})
```
### Running Specific Tests
```bash
# Run unit tests for web
yarn workspace web test
# Run tests matching pattern
yarn test --testPathPattern="profile"
# Run E2E tests
yarn test:e2e
```
### Test Coverage
Aim for meaningful test coverage. Focus on:
- Business logic
- User interactions
- Error handling
- Edge cases
## Pull Request Guidelines
### Before Submitting
1. **Run all tests**: `yarn test`
2. **Run linter**: `yarn lint`
3. **Run type check**: `yarn typecheck`
4. **Update documentation** if needed
5. **Rebase on main** if necessary
### Pull Request Format
**Title**: Clear, descriptive title
**Description**:
```markdown
## Summary
Brief description of changes
## Changes
- Added compatibility score to profile cards
- Updated search algorithm for better results
## Testing
- Added unit tests for scoring algorithm
- Tested manually with synthetic data
## Screenshots (if UI changes)
```
### PR Checklist
- [ ] Code follows style guidelines
- [ ] Tests added/updated and passing
- [ ] Documentation updated
- [ ] No console.log statements (except debugging)
- [ ] No debug code left behind
### Review Process
1. Maintainers review within 48 hours
2. Address feedback promptly
3. Do not open new PRs for changes - update existing one
4. Squash commits before merging
## Commit Message Guidelines
Follow [Conventional Commits](https://www.conventionalcommits.org/):
```
<type>(<scope>): <description>
[optional body]
[optional footer]
```
### Types
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation
- `style`: Formatting
- `refactor`: Code restructuring
- `test`: Tests
- `chore`: Maintenance
### Examples
```text ```text
fix(api): handle 500 error on invalid payload feat(profiles): add compatibility scoring algorithm
fix(api): handle rate limiting gracefully
docs(readme): update installation instructions
refactor(auth): simplify token refresh logic
test(profiles): add unit tests for scoring
``` ```
Types include: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`. ## Documentation
## Test Everything ### Updating Documentation
If the project has tests, run them. If it doesnt, write some. Do **not** submit code that hasn't been tested. - Update relevant README files
- Add JSDoc comments to complex functions
- Update the `/docs` folder for architectural changes
```bash ### API Documentation
# Example for Node.js
npm test
```
No exceptions. If you don't validate your changes, your PR will be closed. API docs are auto-generated and available at:
## Lint & Format - Production: https://api.compassmeet.com
- Local: http://localhost:8088 (when running locally)
Ensure code matches the project style. If the repo uses a linter or formatter, run them: ## Questions and Support
```bash - **Discord**: https://discord.gg/8Vd7jzqjun
npm run lint - **Email**: hello@compassmeet.com
npm run format - **GitHub Issues**: For bug reports and feature requests
```
Or whatever command is defined in the repo. ---
## Write a Good Pull Request
When opening a pull request:
* **Title**: Describe what the PR does, clearly and specifically.
* **Description**: Explain the context. Link related issues (use `Fixes #123` if applicable).
* **Checklist**:
* [ ] My code is clean and follows the style guide
* [ ] Ive added or updated tests
* [ ] Ive run all tests and they pass
* [ ] Ive documented my changes (if necessary)
## Code Review Process
* PRs are reviewed by maintainers or core contributors.
* If feedback is given, respond and push updates. Do **not** open new PRs for changes to an existing one.
* PRs that are incomplete, sloppy, or violate the above will be closed.
## Don't Do This
* Dont commit directly to `main`
* Dont submit multiple unrelated changes in a single PR
* Dont ignore CI/test failures
* Dont expect hand-holding—read the docs and the source first
## Security Issues
Do **not** open public issues for security vulnerabilities. Email the development team instead.
## License
By contributing, you agree that your code will be licensed under the same license as the rest of the project.
Thank you for contributing to Compass! Together we're building a platform for meaningful connections.

674
LICENSE
View File

@@ -1,21 +1,661 @@
MIT License GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (c) 2025 BayesBond Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy Preamble
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The GNU Affero General Public License is a free, copyleft license for
copies or substantial portions of the Software. software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR The licenses for most software and other practical works are designed
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, to take away your freedom to share and change the works. By contrast,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE our General Public Licenses are intended to guarantee your freedom to
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER share and change all versions of a program--to make sure it remains free
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, software for all its users.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

21
LICENSE-MIT Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 polylove, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
NOTICE Normal file
View File

@@ -0,0 +1,6 @@
Modifications License (AGPL-3.0)
Portions of this software have been modified by Compass (c) 2025.
These modifications are licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
The original software remains MIT-licensed (c) 2025 polylove, LLC.

340
README.md
View File

@@ -1,91 +1,343 @@
# BayesBond ![Vercel](https://deploy-badge.vercel.app/vercel/compass)
[![CD](https://github.com/CompassConnections/Compass/actions/workflows/cd.yml/badge.svg)](https://github.com/CompassConnections/Compass/actions/workflows/cd.yml)
[![CD API](https://github.com/CompassConnections/Compass/actions/workflows/cd-api.yml/badge.svg)](https://github.com/CompassConnections/Compass/actions/workflows/cd-api.yml)
[![CD Android](https://github.com/CompassConnections/Compass/actions/workflows/cd-android.yml/badge.svg)](https://github.com/CompassConnections/Compass/actions/workflows/cd-android.yml)
[![CI](https://github.com/CompassConnections/Compass/actions/workflows/ci.yml/badge.svg)](https://github.com/CompassConnections/Compass/actions/workflows/ci.yml)
[![CI E2E](https://github.com/CompassConnections/Compass/actions/workflows/ci-e2e.yml/badge.svg)](https://github.com/CompassConnections/Compass/actions/workflows/ci-e2e.yml)
[![codecov](https://codecov.io/gh/CompassConnections/Compass/branch/main/graph/badge.svg)](https://codecov.io/gh/CompassConnections/Compass)
[![Users](https://img.shields.io/badge/Users-500%2B-blue?logo=myspace)](https://www.compassmeet.com/stats)
This repository provides the source code for [BayesBond](https://bayesbond.vercel.app), a web application where rational thinkers can bond and form deep 1-1 # Compass
relationships in a fully transparent and efficient way. It just got released—please share it with anyone who would benefit from it!
To contribute, please submit a pull request or issue, or fill out this [form](https://forms.gle/tKnXUMAbEreMK6FC6) for suggestions and collaborations. This repository contains the source code for [Compass](https://compassmeet.com) — a transparent platform for forming
deep, authentic 1-on-1 connections with clarity and efficiency.
## Features ## Features
- Extremely detailed profiles for deep connections - Extremely detailed profiles for deep connections
- Radically transparent: user base fully searchable - Radically transparent: user base fully searchable
- Free, ad-free, not for profit - Free, ad-free, not for profit (supported by donations)
- Supported by donation - Created, hosted, maintained, and moderated by volunteers
- Open source - Open-source
- Democratically governed - Democratically governed
The full description is available [here](https://martinbraquet.com/meeting-rational). You can find a lot of interesting info in the [About page](https://www.compassmeet.com/about) and
the [FAQ](https://www.compassmeet.com/faq) as well.
A detailed description of the early vision is also available in
this [blog post](https://martinbraquet.com/meeting-rational) (you can disregard the parts about rationality, as Compass
shifted to a more general audience).
**We cant do this alone.** Whatever your skills—coding, design, writing, moderation, marketing, or even small
donations—you can make a real difference. [Contribute](https://www.compassmeet.com/support) in any way you can and help
our community thrive!
![Demo](https://raw.githubusercontent.com/CompassConnections/assets/refs/heads/main/assets/demo-2x.gif)
## To Do ## To Do
No contribution is too small—whether its changing a color, resizing a button, tweaking a font, or improving wording.
Bigger contributions like adding new profile fields, building modules, or improving onboarding are equally welcome. The
goal is to make the platform better step by step, and every improvement counts. If you see something that could be
clearer, smoother, or more engaging, **please jump in**!
The complete, official list of tasks is
available [here on ClickUp](https://sharing.clickup.com/90181043445/l/h/6-901810339879-1/bbfd32f4f4bf64b). If you are
working on one task, just assign it to yourself and move its status to "in progress". If there is also a GitHub issue
for that task, assign it to yourself as well.
To have edit access to the ClickUp workspace, you need an admin to manually give you permission (one time thing). To do
so, use your preferred option:
- Ask or DM an admin on [Discord](https://discord.gg/8Vd7jzqjun)
- Email hello@compassmeet.com
- Raise an issue on GitHub
If you want to add tasks without creating an account, you can simply email
```
a.t.901810339879.u-276866260.b847aba1-2709-4f17-b4dc-565a6967c234@tasks.clickup.com
```
Put the task title in the email subject and the task description in the email content.
Here is a tailored selection of things that would be very useful. If you want to help but dont know where to start,
just ask us on [Discord](https://discord.gg/8Vd7jzqjun).
- [x] Authentication (user/password and Google Sign In) - [x] Authentication (user/password and Google Sign In)
- [x] Set up PostgreSQL in Production with supabase or prisma console (can stick with SQLite in dev / local) - [x] Set up PostgreSQL in Production with supabase
- [x] Set up hosting (vercel) - [x] Set up web hosting (vercel)
- [x] Ask for detailed info per profile upon registration (intellectual interests, location, cause areas, personality type, conflict style, desired type of connection, prompt answers, gender, etc.) - [x] Set up backend hosting (google cloud)
- [x] Ask for detailed info upon registration (location, desired type of connection, prompt answers, gender, etc.)
- [x] Set up page listing all the profiles - [x] Set up page listing all the profiles
- [x] Search through all the profile variables - [x] Search through most profile variables
- [ ] (Set up chat / direct messaging) - [x] Set up chat / direct messaging
- [ ] Set up domain name (https://bayesbond.com) - [x] Set up domain name (compassmeet.com)
- [ ] Cover more than 90% with tests (unit, integration, e2e)
- [x] Add Android mobile app
- [ ] Add iOS mobile app
- [x] Add better onboarding (tooltips, modals, etc.)
- [ ] Add modules to learn more about each other (personality test, conflict style, love languages, etc.)
- [ ] Add modules to improve interpersonal skills (active listening, nonviolent communication, etc.)
- [ ] Add calendar integration and scheduling
- [x] Add events (group calls, in-person meetups, etc.)
#### Secondary To Do #### Secondary To Do
Any action item is open to anyone for collaboration, but the following ones are particularly easy to do for first-time contributors. Everything is open to anyone for collaboration, but the following ones are particularly easy to do for first-time
contributors.
- [ ] Clean up terms and conditions - [x] Clean up learn more page
- [ ] Clean up privacy notice
- [ ] Clean up learn more page
- [x] Add dark theme - [x] Add dark theme
- [ ] Cover with tests - [x] Add profile fields (intellectual interests, cause areas, personality type, etc.)
- [ ] Add profile fields: conflict style
- [ ] Add profile fields: timezone
- [ ] Add translations: Italian, Dutch, Hindi, Chinese, etc.
- [x] Add filters to search through remaining profile fields (politics, religion, education level, etc.)
- [ ] Make the app more user-friendly and appealing (UI/UX)
- [ ] Clean up terms and conditions (convert to Markdown)
- [ ] Clean up privacy notice (convert to Markdown)
- [ ] Add other authentication methods (GitHub, Facebook, Apple, phone, etc.)
- [x] Add email verification
- [x] Add password reset
- [x] Add automated welcome email
- [ ] Security audit and penetration testing
- [x] Make `deploy-api.sh` run automatically on push to `main` branch
- [x] Create settings page (change email, password, delete account, etc.)
- [ ] Improve [financials](web/public/md/financials.md) page (donor / acknowledgments, etc.)
- [x] Improve loading sign (e.g., animation of a compass moving around)
- [x] Show compatibility score in profile page
## Implementation ## Implementation
The web app is coded in Typescript using React as front-end and Prisma as back-end. It includes: The web app is coded in Typescript using React as front-end. It includes:
- [NextAuth.js v4](https://next-auth.js.org/) - [Supabase](https://supabase.com/) for the PostgreSQL database
- [Prisma Postgres](https://www.prisma.io/postgres) - [Google Cloud](https://console.cloud.google.com) for hosting the backend API
- [Prisma ORM](https://www.prisma.io/orm) - [Firebase](https://firebase.google.com/) for authentication and media storage
- Vercel - [Vercel](https://vercel.com/) for hosting the front-end
## Development ## Development
After cloning the repo and navigating into it, install dependencies: Below are the steps to contribute. If you have any trouble or questions, please don't hesitate to open an issue or
contact us on [Discord](https://discord.gg/8Vd7jzqjun)! We're responsive and happy to help.
``` ### Installation
npm install
```
You now need to configure your database connection via an environment variable. Fork the [repo](https://github.com/CompassConnections/Compass) on GitHub (button in top right). Then, clone your repo
and navigating into it:
First, create an `.env` file:
```bash ```bash
cp .env.example .env git clone https://github.com/<your-username>/Compass.git
cd Compass
``` ```
To ensure your authentication works properly, you'll also need to set the `AUTH_SECRET` [env var for NextAuth.js] Install `yarn` (if not already installed):
(https://next-auth.js.org/configuration/options). You can generate such a random 32-character string with:
```bash ```bash
npx auth secret npm install --global yarn
``` ```
In the end, your entire `.env` file should look similar to this (but using _your own values_ for the env vars): Then, install the dependencies for this project:
```bash ```bash
DATABASE_URL="file:./dev.db" yarn install --frozen-lockfile
AUTH_SECRET="gTwLSXFeNWFRpUTmxlRniOfegXYw445pd0k6JqXd7Ag="
``` ```
Run the following commands to set up your local development database and Prisma schema: ### Tests
Make sure the Jest tests pass:
```bash ```bash
npx prisma migrate dev --name init yarn test
``` ```
Note that your local database will be made of synthetic data, not real users. This is fine for development and testing.
If they don't and you can't find out why, simply raise an issue! Sometimes it's something on our end that we overlooked.
### Running the Development Server
Start the development server: Start the development server:
```bash ```bash
npm run dev yarn dev
``` ```
Once the server is running, visit http://localhost:3000 to start using the app. Once the server is running, visit http://localhost:3000 to start using the app. You can sign up and visit the profiles;
you should see a few synthetic profiles.
See [development.md](docs/development.md) for additional instructions, such as adding new profile features. Note: it's normal if page loading locally is much slower than the deployed version. It can take up to 10 seconds, it
would be great to improve that though!
#### Full isolation
Running `yarn dev:isolated` spins up a local Supabase and Firebase emulator instead of pointing at the shared remote
database. This is strongly recommended for day-to-day development:
- **Freedom** — Reset, wipe, and reseed your local database as many times as you want without affecting other
contributors.
- **No conflicts** — Multiple contributors can work simultaneously without stepping on each other's data or schema.
- **Works offline** — No internet required once services are started locally.
- **Faster** — No network latency on every database query.
However, running in full isolation requires installing several heavy dependencies:
- **Docker** (~500MB) — runs the Supabase Postgres container
- **Supabase CLI** — manages the local Supabase stack (10+ Docker containers, ~1-2GB RAM when running)
- **Java 21+** (~300MB) — required by Firebase emulators
- **Firebase CLI** — manages the local Firebase emulators
First startup is slow (30-60s) and the stack uses significant memory. If your machine has less than 8GB RAM, you may
notice slowdowns.
If this feels like too much, you can skip isolation entirely — `yarn dev` works out of the box against the shared remote
and is perfectly fine for most contributions, especially UI changes, wording fixes, or anything that doesn't touch the
database or authentication.
###### Setup instructions
As always, don't hesitate to raise an issue if you run into any problems!
Docker
```bash
# Ubuntu/Debian (native - recommended over snap)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Log out and back in for group changes to take effect
# macOS
brew install --cask docker
# Or download from https://www.docker.com/products/docker-desktop
# Verify
docker --version
```
Supabase CLI
```bash
# Verify it got installed from `yarn install`
npx supabase --version
```
Java 21+
```bash
# Ubuntu/Debian
sudo apt install openjdk-21-jdk
# macOS
brew install openjdk@21
# Verify (must be 21+)
java -version
```
Firebase CLI
```bash
npm install -g firebase-tools
# Verify
firebase --version
```
Run in isolation
```bash
yarn dev:isolated
```
Visit `http://localhost:3000` as usual. Your local database comes preloaded with synthetic test profiles so the app
looks and feels like the real thing.
### Contributing
Now you can start contributing by making changes and submitting pull requests!
We recommend using a good code editor (VSCode, WebStorm, Cursor, etc.) with Typescript support and a good AI assistant (
GitHub Copilot, etc.) to make your life easier. To debug, you can use the browser developer tools (F12), specifically:
- Components tab to see the React component tree and props (you need to install
the [React Developer Tools](https://react.dev/learn/react-developer-tools) extension)
- Console tab for errors and logs
- Network tab to see the requests and responses
- Storage tab to see cookies and local storage
You can also add `console.log()` statements in the code.
If you are new to Typescript or the open-source space, you could start with small changes, such as tweaking some web
components or improving wording in some pages. You can find those files in `web/public/md/`.
##### Resources
There is a lot of documentation in the [docs](docs) folder and across the repo, namely:
- [Next.js.md](docs/Next.js.md) for core fundamentals about our web / page-rendering framework.
- [knowledge.md](docs/knowledge.md) for general information about the project structure.
- [development.md](docs/development.md) for additional instructions, such as adding new profile fields or languages.
- [TESTING.md](docs/TESTING.md) for how to write tests.
- [PERFORMANCE_OPTIMIZATION.md](docs/PERFORMANCE_OPTIMIZATION.md) for frontend, backend, and database performance best practices.
- [DATABASE_CONNECTION_POOLING.md](docs/DATABASE_CONNECTION_POOLING.md) for database connection management and troubleshooting.
- [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for resolving common development issues.
- [web](web) for the web.
- [backend/api](backend/api) for the backend API.
- [android](android) for the Android app.
There are a lot of useful scripts you can use in the [scripts](scripts) folder.
### Submission
Add the original repo as upstream for syncing:
```bash
git remote add upstream https://github.com/CompassConnections/Compass.git
```
Create a new branch for your changes:
```bash
git checkout -b <branch-name>
```
Make changes, then stage and commit:
```bash
git add .
git commit -m "Describe your changes"
```
Push branch to your fork:
```bash
git push origin <branch-name>
```
Finally, open a Pull Request on GitHub from your `fork/<branch-name>``CompassConnections/Compass` main branch.
### Environment Variables
Almost all the features will work out of the box, so you can skip this step and come back later if you need to test the
following services: email, geolocation.
We can't make the following information public, for security and privacy reasons:
- Database, otherwise anyone could access all the user data (including private messages)
- Firebase, otherwise anyone could remove users or modify the media files
- Email, analytics, and location services, otherwise anyone could use the service plans Compass paid for and run up the
bill.
That's why we separate all those services between production and development environments, so that you can code freely
without impacting the functioning of the deployed platform.
Contributors should use the default keys for local development. Production uses a separate environment with stricter
rules and private keys that are not shared.
If you do need one of the few remaining services, you need to set them up and store your own secrets as environment
variables. To do so, simply open `.env` and fill in the variables according to the instructions in the file.
## Acknowledgements
This project is built on top of [manifold.love](https://github.com/sipec/polylove), an open-source dating platform
licensed under the MIT License. We greatly appreciate their work and contributions to open-source, which have
significantly aided in the development of some core features such as direct messaging, prompts, and email notifications.
We invite the community to explore and contribute to other open-source projects like manifold.love as well, especially
if you're interested in functionalities that deviate from Compass' ideals of deep, intentional connections.

View File

@@ -4,9 +4,121 @@
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | ------- | ------------------ |
| 1.0.0 | :white_check_mark: | | 1.10.x | :white_check_mark: |
| 1.9.x | :white_check_mark: |
| < 1.9.0 | :x: |
## Reporting a Vulnerability ## Reporting a Vulnerability
Contact the development team to report a vulnerability. You should receive updates within a week. If you discover a security vulnerability within Compass, please send an email to hello@compassmeet.com. All security vulnerabilities will be promptly addressed.
Please do not publicly disclose the vulnerability until it has been resolved.
## Security Practices
Compass takes security seriously and implements several best practices to protect user data and ensure application integrity.
### Authentication & Authorization
- **Firebase Authentication**: User authentication is handled by Firebase Auth, which provides industry-standard security for user credentials
- **JWT Tokens**: Secure token-based authentication for API access
- **Role-Based Access Control**: Different permission levels for users, moderators, and administrators
- **Session Management**: Secure session handling with automatic timeout
### Data Protection
- **Encryption at Rest**: Sensitive data is encrypted in the database
- **Encryption in Transit**: All communications use HTTPS/TLS encryption
- **Environment Variables**: Secrets are managed through secure environment variable configuration
- **Data Minimization**: Only necessary data is collected and stored
### Input Validation
- **Zod Validation**: Strong type checking and validation for all API inputs
- **Sanitization**: Input sanitization to prevent injection attacks
- **Rate Limiting**: Protection against brute force and denial of service attacks
### API Security
- **CORS Configuration**: Restricted cross-origin resource sharing policies
- **Rate Limiting**: Per-endpoint rate limiting to prevent abuse
- **Authentication Middleware**: All protected endpoints require valid authentication
- **Input Validation**: Comprehensive validation of all API inputs
### Database Security
- **Row Level Security**: Fine-grained access control at the database level
- **Parameterized Queries**: Prevention of SQL injection attacks
- **Audit Logging**: Tracking of database access and modifications
- **Regular Backups**: Automated database backups for disaster recovery
### Third-Party Services
- **Firebase Security Rules**: Strict security rules for Firestore and Storage
- **Supabase RLS**: Row-level security policies for PostgreSQL
- **Secrets Management**: Secure storage of API keys and credentials
### Development Practices
- **Code Reviews**: All changes reviewed by multiple developers
- **Automated Testing**: Security-focused tests integrated into CI/CD pipeline
- **Dependency Management**: Regular updates and security scanning of dependencies
- **Security Audits**: Periodic security assessments and penetration testing
## Common Security Issues and Resolutions
### XSS Prevention
- **Content Security Policy**: Strict CSP headers to prevent cross-site scripting
- **Input Sanitization**: All user-generated content is sanitized before display
- **Output Encoding**: Proper encoding of user data in HTML contexts
### CSRF Protection
- **SameSite Cookies**: CSRF protection through SameSite cookie attributes
- **Anti-Forgery Tokens**: Token-based protection for state-changing operations
### Injection Attacks
- **SQL Injection**: Parameterized queries and prepared statements
- **Command Injection**: Input validation and sanitization
- **Script Injection**: Content Security Policy and input filtering
## Incident Response
In the event of a security incident:
1. **Immediate Containment**: Isolate affected systems
2. **Investigation**: Determine scope and impact of breach
3. **Remediation**: Apply fixes and security patches
4. **Notification**: Inform affected users and stakeholders
5. **Review**: Post-incident analysis and process improvement
## Compliance
Compass aims to comply with relevant data protection regulations:
- **GDPR**: General Data Protection Regulation compliance
- **CCPA**: California Consumer Privacy Act compliance
- **Data Retention**: Clear policies for data retention and deletion
## Third-Party Security
We regularly audit third-party services for:
- Security certifications and compliance
- Regular security updates and patches
- Data handling and privacy practices
- Incident response procedures
## Security Contact
For security-related inquiries, contact:
- Email: hello@compassmeet.com
- Response Time: Within 24 hours for critical issues
- Disclosure Policy: Coordinated disclosure with 90-day timeline
---
_Last Updated: March 2026_

0
android/.aiexclude Normal file
View File

102
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,102 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
/app/release/

531
android/README.md Normal file
View File

@@ -0,0 +1,531 @@
# Compass Android WebView App
This folder contains the source code for the Android application of Compass.
A hybrid mobile app built with **Next.js (TypeScript)** frontend, **Firebase backend**, and wrapped as a **Capacitor
WebView** for Android. In the future it may contain native code as well.
This document describes how to:
1. Build and run the web frontend and backend locally
2. Sync and build the Android WebView wrapper
3. Debug, sign, and publish the APK
4. Enable Google Sign-In and push notifications
---
## 1. Project Overview
The app is a Capacitor Android project that loads the local Next.js assets inside a WebView.
During development, it can instead load the local frontend (`http://10.0.2.2:3000`) and backend (
`http://10.0.2.2:8088`).
Firebase handles authentication and push notifications.
Google Sign-In is supported natively in the WebView via the Capacitor Social Login plugin.
Project Structure
- `app/src/main/java/com/compass/app`: Contains the Java/Kotlin source code for the Android application.
- `app/src/main/res`: Contains the resources for the application, such as layouts, strings, and images.
- `app/build.gradle`: The Gradle build file for the Android application module.
- `build.gradle`: The top-level Gradle build file for the project.
- `AndroidManifest.xml`: The manifest file that describes essential information about the application.
### **Why Local Is the Default**
- **Performance:** Local assets load instantly, without network latency.
- **Reliability:** Works offline or in poor connectivity environments.
- **App Store policy compliance:** Apple and Google generally prefer that the main experience doesnt depend on a remote
site (for security, review, and performance reasons).
- **Version consistency:** The web bundle is versioned with the app, ensuring no breaking updates outside your control.
When Remote (No Local Assets) Is sometimes Used
Loading from a **remote URL** (e.g. `https://compassmeet.com`) is **less common**, but seen in a few cases:
- **Internal enterprise apps** where the WebView just wraps an existing web portal.
- **Dynamic content** or **frequent updates** where pushing a new web build every time through app stores would be too
slow.
- To leverage the low latency of ISR and SSR.
However, this approach requires:
- Careful handling of **CORS**, **SSL**, and **login/session** persistence.
- Compliance with **Google Play policies** (they may reject apps that are “just a webview of a website” unless theres
meaningful native integration).
**A middle ground we use:**
- The app ships with **local assets** for core functionality.
- The app **fetches remote content or updates** (e.g., via Capacitor Live Updates, Ionic Appflow).
## 2. Prerequisites
### Required Software
| Tool | Version | Purpose |
|----------------|---------|------------------------------------|
| Node.js | 22+ | For building frontend/backend |
| yarn | latest | Package manager |
| Java | 21 | Required for Android Gradle plugin |
| Android Studio | latest | For building and signing APKs |
| Capacitor CLI | latest | Android bridge |
| OpenJDK | 21 | JDK for Gradle |
### Environment Setup
```bash
sudo apt install openjdk-21-jdk
sudo update-alternatives --config java
# Select Java 21
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java -version
javac -version
````
---
## 3. Build and Run the Web App
```
yarn install
yarn build-web-view
```
### Local mode
If you want the webview to load from your local web version of Compass, run the web app.
In root directory:
```bash
export NEXT_PUBLIC_LOCAL_ANDROID=1
yarn dev # or prod
```
* Runs Next.js frontend at `http://localhost:3000`
* Runs backend at `http://10.0.2.2:8088`
### Deployed mode
If you want the webview to load from the deployed web version of Compass (like at compassmeet.com), no web app to
run.
---
## 5. Android WebView App Setup
### Install dependencies
```
cd android
./gradlew clean
```
Sync web files and native plugins with Android, for offline fallback. In root:
```
export NEXT_PUBLIC_LOCAL_ANDROID=1 # if running your local web Compass
yarn build-web-view # if you made changes to web app
npx cap sync android
```
### Load from site
During local development, open Android Studio project and run the app on an emulator or your physical device.
To use an emulator:
```
npx cap open android
```
To use a physical device for the local web version, you need your mobile and computer to be on the same network / Wi-Fi
and point the URL (`LOCAL_BACKEND_DOMAIN` in the code) to your computer IP address (for example, `192.168.1.3:3000`).
You also need to set
```
export NEXT_PUBLIC_WEBVIEW_DEV_PHONE=1
```
Then adb install the app your phone (or simply run it from Android Studio on your phone) and the app should be loading
content directly from the local code on your computer. You can make changes in the code and it will refresh instantly on
the phone.
Building the Application:
1. Open Android Studio.
2. Click on "Open an existing Android Studio project".
3. Navigate to the `android` folder in this repository and select it.
4. Wait for Android Studio to index the project and download any necessary dependencies.
5. Connect your Android device via USB or set up an Android emulator.
6. Click on the "Run" button (green play button) in Android Studio to build and run the application.
7. Select your device or emulator and click "OK".
8. The application should now build and launch on your device or emulator.
---
## 6. Building the APK
### From Android Studio
- If you want to generate a signed APK for release, go to "Build" > "Generate Signed Bundle / APK..." and follow the
prompts.
- Make sure to test the application thoroughly on different devices and Android versions to ensure compatibility.
### Debug build
```bash
cd android
./gradlew assembleDebug
```
Outputs:
```
android/app/build/outputs/apk/debug/app-debug.apk
```
### Install on emulator
```bash
adb install -r app/build/outputs/apk/debug/app-debug.apk
```
### Release build (signed)
1. Generate a release keystore:
```bash
keytool -genkey -v -keystore release-key.keystore -alias compass \
-keyalg RSA -keysize 2048 -validity 10000
```
2. Add signing config to `android/app/build.gradle`
3. Build:
```bash
./gradlew assembleRelease
```
### Release on App Stores
To release on the app stores, you need to submit the .aab files, which are not signed, instead of APK. Google or Apple
will then sign it with their own key.
However, it's recommended to use the GitHub Action for better version control and automation. See section below:
`Deploy to Play Store`.
---
## 9. Debugging
Client logs from the emulator on Chrome can be accessed at:
```
chrome://inspect/#devices
```
Backend logs can be accessed from the output of `yarn prod / dev` like in the web application.
Java/Kotlin logs can be accessed via Android Studio's Logcat.
```
adb logcat | grep CompassApp
adb logcat | grep com.compassconnections.app
adb logcat | grep Capacitor
```
You can also add this inside `MainActivity.java`:
```java
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.d("WebView", consoleMessage.message());
return true;
}
});
```
---
## 10. Deploy to Play Store
The best way to deploy to the Play Store is to use the GitHub Action defined
in [cd-android.yml](../.github/workflows/cd-android.yml). You
increase the version in `android/app/build.gradle`, commit to the main branch and it will automatically build the
release APK and upload it to the Play Store.
To deploy manually, follow these steps:
1. Sign the release APK or AAB.
2. Verify package name matches Firebase settings (`com.compassconnections.app`).
3. Upload to Google Play Console.
4. Add Privacy Policy and content rating (one time).
5. Submit for review. It takes around an hour for it to be approved and appear in the store.
---
## 11. Common Issues
| Problem | Cause | Fix |
|--------------------------------------|----------------------------------------|---------------------------------------------------------------------|
| `INSTALL_FAILED_UPDATE_INCOMPATIBLE` | Old APK signed with different key | Uninstall old app first |
| `Account reauth failed [16]` | Missing or incorrect SHA-1 in Firebase | Re-add SHA-1 of keystore |
| App opens in Firefox | Missing `WebViewClient` override | Fix `shouldOverrideUrlLoading` |
| APK > 1 GB | Cached webpack artifacts included | Add `.next/` and `/public/cache` to `.gitignore` and build excludes |
---
## 13. Local Development Workflow
```bash
# Terminal 1
export NEXT_PUBLIC_LOCAL_ANDROID=1
yarn dev # or prod
# Terminal 2: start frontend
export NEXT_PUBLIC_LOCAL_ANDROID=1
yarn build-web-view # if you made changes to web app
npx cap sync android
# Run on emulator or device
```
---
## 14. Deployment Workflow
To deploy manually:
```bash
# Build web app for production and Sync assets to Android
yarn build-sync-android
# Build signed release APK in Android Studio
```
But prefer using the GitHub Action, see `Deploy to Play Store`.
---
## Live Updates
Note: As of early 2026, we don't use the live update anymore because the free plan is too limited for our use case. To
update the android app, we need to stick to the normal release process on the app stores.
To avoid releasing to the app stores after every code update in the web pages, we build the new bundle and store it in
Capawesome Cloud (an alternative to Ionic). To add a new update, increment the version number
in [capawesome.json](capawesome.json) and push to main (or make a PR to main). A GitHub Action will automatically build
the new bundle and push it to Capawesome.
You can also do so locally if you have admin access. First, you need to do this one-time setup:
```
npm install -g @capawesome/cli@latest
npx @capawesome/cli login
```
Then, run this to build your local assets and push them to Capawesome. Once done, each mobile app user will receive a
notice that there is a new update available, which they can approve to download.
```
yarn android:live-update
```
That's all. So you should run the lines above every time you want your web updates pushed to main (which essentially
updates the web app) to update the mobile app as well.
There is a limit of 100 monthly active user per month, though. So we may need to pay or create our custom limit as we
scale. Next plan is $9 / month and allows 1000 MAUs.
- ∞ Live Updates
- 100 Monthly Active Users
- 500 MB of Storage (around 10 MB per update, but we just delete the previous ones)
- 5 GB of Bandwidth
---
## 15. Resources
* [Capacitor Docs](https://capacitorjs.com/docs)
* [Firebase Android Setup](https://firebase.google.com/docs/android/setup)
* [FCM HTTP API](https://firebase.google.com/docs/cloud-messaging/send-message)
* [Next.js Deployment](https://nextjs.org/docs/deployment)
# Useful Commands
- To build the project: `./gradlew assembleDebug`
- To run unit tests: `./gradlew test`
- To run instrumentation tests: `./gradlew connectedAndroidTest`
- To clean the project: `./gradlew clean`
- To install dependencies: Open Android Studio and it will handle dependencies automatically.
- To update dependencies: Modify the `build.gradle` files and sync the project in Android Studio.
- To generate a signed APK: Use the "Generate Signed Bundle / APK..." option in Android Studio.
- To lint the project: `./gradlew lint`
- To check for updates to the Android Gradle Plugin: `./gradlew dependencyUpdates`
- To run the application on a connected device or emulator: `./gradlew installDebug`
- To view the project structure: Use the "Project" view in Android Studio.
- To analyze the APK: `./gradlew analyzeRelease`
- To run ProGuard/R8: `./gradlew minifyRelease`
- To generate documentation: `./gradlew javadoc`
# One time setups
Was already done for Compass, so you only need to do the steps below if you create a project separated from Compass.
## Configure Firebase
### In Firebase Console
1. Add a **Web app** → obtain `firebaseConfig`
2. Add an **Android app**
* Package name: `com.compassconnections.app`
* Add your SHA-1 and SHA-256 fingerprints (see below)
* Download `google-services.json` and put it in:
```
android/app/google-services.json
```
### To get SHA-1 for debug keystore
```bash
keytool -list -v \
-keystore ~/.android/debug.keystore \
-alias androiddebugkey \
-storepass android \
-keypass android
```
Add both SHA-1 and SHA-256 to Firebase.
## 7. Google Sign-In (Web + Native)
In Firebase Console:
* Enable **Google** provider under *Authentication → Sign-in method*
* Add your **Android SHA-1**
* Add your **Web OAuth client ID**
In your code:
```ts
import {googleNativeLogin} from 'web/lib/service/android-push'
```
## 8. Push Notifications (FCM)
### Setup FCM
* Add Firebase Cloud Messaging to your project
* Include `google-services.json` under `android/app/`
* Add in `android/build.gradle`:
```gradle
classpath 'com.google.gms:google-services:4.3.15'
```
* Add at the bottom of `android/app/build.gradle`:
```gradle
apply plugin: 'com.google.gms.google-services'
```
### Test notification
```ts
const message = {
notification: {
title: "Test Notification",
body: "Hello from Firebase Admin SDK"
},
token: "..."
};
initAdmin()
await admin.messaging().send(message)
.then(response => console.log("Successfully sent message:", response))
.catch(error => console.error("Error sending message:", error));
```
---
## Deep link / custom scheme
A **custom scheme** is a URL protocol that your app owns.
Example:
```
com.compassconnections.app://auth
```
When Android (or iOS) sees a redirect to one of these URLs, it **launches your app** and passes it the URL data. It's
useful to open links in the app instead of the browser. For example, if there's a link to Compass on Discord and we
click on it on a mobile device that has the app, we want the link to open in the app instead of the browser.
You register this scheme in your `AndroidManifest.xml` so Android knows which app handles it.
## Automatic Workflow for App Release
Below is a **minimal, production-ready GitHub Actions setup** that:
* Builds on push to `main`
* Checks if `versionCode` increased
* Only builds if it did
* Signs the AAB
* Uploads to Google Play (internal track)
#### A. Create Play Console API access
1. Go to google cloud console. Create service account without selecting any specific permission or roles. Just copy
paste the email address and generate a JSON key.
2. Go to **Google Play Console**
3. Invite user, enter the service account email address.
5. Give it:
* Release Manager role
You will store the JSON key in GitHub Secrets.
---
#### B. Prepare Keystore
If you already sign locally, you have a `.jks` or `.keystore` file.
Base64 encode it:
```bash
base64 my-release-key.keystore
```
Copy the output.
---
#### C. GitHub Secrets
In your GitHub repo:
Settings → Secrets and variables → Actions → New repository secret
Add:
```
ANDROID_KEYSTORE_BASE64
ANDROID_KEYSTORE_PASSWORD
ANDROID_KEY_PASSWORD
PLAY_SERVICE_ACCOUNT_JSON
```
For the JSON:
* Paste full raw JSON (not base64)
#### GitHub Actions YAML
We compare:
* `versionCode` in current commit
* `versionCode` in previous commit
If not increased → skip build.
We extract from `app/build.gradle` using grep.
See `.github/workflows/android-release.yml` for all details.

3
android/app/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/build/*
!/build/.npmkeep
/google-services.json

79
android/app/build.gradle Normal file
View File

@@ -0,0 +1,79 @@
apply plugin: 'com.android.application'
android {
namespace "com.compassconnections.app"
compileSdk 36
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
defaultConfig {
applicationId "com.compassconnections.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 84
versionName "1.17.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
buildFeatures {
buildConfig = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField "boolean", "ENABLE_WEBVIEW_DEBUG", "false"
}
debug {
buildConfigField "boolean", "ENABLE_WEBVIEW_DEBUG", "true"
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
// Import the Firebase BoM
implementation platform('com.google.firebase:firebase-bom:34.4.0')
// TODO: Add the dependencies for Firebase products you want to use
// When using the BoM, don't specify versions in Firebase dependencies
implementation 'com.google.firebase:firebase-analytics'
// Add the dependencies for any other desired Firebase products
// https://firebase.google.com/docs/android/setup#available-libraries
implementation 'com.google.android.gms:play-services-auth:21.5.1'
implementation 'com.google.firebase:firebase-auth:24.0.1'
implementation 'com.google.android.play:app-update:2.1.0'
implementation 'com.google.android.play:app-update-ktx:2.1.0'
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}

View File

@@ -0,0 +1,24 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-status-bar')
implementation project(':capawesome-capacitor-live-update')
implementation project(':capgo-capacitor-social-login')
}
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}

21
android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,26 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,86 @@
<?xml version="1.1" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:fitsSystemWindows="true"
android:requestLegacyExternalStorage="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="compassmeet.com" />
<data android:scheme="https" />
<data android:host="www.compassmeet.com" />
</intent-filter>
<!-- <intent-filter>-->
<!-- <action android:name="openapp" />-->
<!-- <category android:name="android.intent.category.DEFAULT" />-->
<!-- <category android:name="android.intent.category.BROWSABLE" />-->
<!-- <data android:scheme="com.compassconnections.app" android:host="details"/>-->
<!-- </intent-filter>-->
<!-- <intent-filter android:autoVerify="true">-->
<!-- <action android:name="android.intent.action.VIEW" />-->
<!-- <category android:name="android.intent.category.DEFAULT" />-->
<!-- <category android:name="android.intent.category.BROWSABLE" />-->
<!-- <data android:scheme="com.compassconnections.app" android:host="auth" />-->
<!-- </intent-filter>-->
</activity>
<!-- <service-->
<!-- android:name=".MyMessagingService"-->
<!-- android:exported="false">-->
<!-- <intent-filter>-->
<!-- <action android:name="com.google.firebase.MESSAGING_EVENT" />-->
<!-- </intent-filter>-->
<!--&lt;!&ndash; <meta-data&ndash;&gt;-->
<!--&lt;!&ndash; android:name="com.google.firebase.messaging.default_notification_channel_id"&ndash;&gt;-->
<!--&lt;!&ndash; android:value="@string/default_notification_channel_id" />&ndash;&gt;-->
<!-- </service>-->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />
<!-- Firebase Cloud Messaging -->
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<!-- Old, can be removed ?-->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
</manifest>

View File

@@ -0,0 +1,353 @@
package com.compassconnections.app;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import com.capacitorjs.plugins.pushnotifications.PushNotificationsPlugin;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.BridgeWebViewClient;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginHandle;
import com.google.android.play.core.appupdate.AppUpdateInfo;
import com.google.android.play.core.appupdate.AppUpdateManager;
import com.google.android.play.core.appupdate.AppUpdateManagerFactory;
import com.google.android.play.core.appupdate.AppUpdateOptions;
import com.google.android.play.core.install.model.AppUpdateType;
import com.google.android.play.core.install.model.UpdateAvailability;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import ee.forgr.capacitor.social.login.GoogleProvider;
import ee.forgr.capacitor.social.login.ModifiedMainActivityForSocialLoginPlugin;
import ee.forgr.capacitor.social.login.SocialLoginPlugin;
public class MainActivity extends BridgeActivity implements ModifiedMainActivityForSocialLoginPlugin {
private String pendingDeepLink = null;
// Declare this at class level
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
Log.i("CompassApp", "Permission granted");
// Permission granted you can show notifications
} else {
Log.i("CompassApp", "Permission denied");
// Permission denied handle gracefully
}
});
private void askNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // API 33
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission not yet granted; request it
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
}
}
}
public class WebAppInterface {
private final Context context;
public WebAppInterface(Context context) {
this.context = context;
}
@JavascriptInterface
public String getPendingDeepLink() {
String link = pendingDeepLink;
pendingDeepLink = null; // consume it
return link;
}
@JavascriptInterface
public void downloadFile(String filename, String content) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+ (API 29+) - Use MediaStore
downloadFileModern(filename, content);
} else {
// Android 9 and below - Use legacy method
downloadFileLegacy(filename, content);
}
// Show success message
runOnUiThread(() ->
Toast.makeText(MainActivity.this, "File downloaded: " + filename, Toast.LENGTH_SHORT).show()
);
} catch (IOException e) {
Log.e("CompassApp", "Failed to download file", e);
runOnUiThread(() ->
Toast.makeText(MainActivity.this, "Download failed: " + e.getMessage(), Toast.LENGTH_SHORT).show()
);
}
}
// For Android 10+ (Scoped Storage)
@RequiresApi(api = Build.VERSION_CODES.Q)
private void downloadFileModern(String filename, String content) throws IOException {
ContentResolver resolver = getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, filename);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(filename));
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
Uri uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
if (uri != null) {
try (OutputStream outputStream = resolver.openOutputStream(uri)) {
if (outputStream != null) {
outputStream.write(content.getBytes(StandardCharsets.UTF_8));
}
}
}
}
// For Android 9 and below
private void downloadFileLegacy(String filename, String content) throws IOException {
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
if (!downloadsDir.exists()) {
downloadsDir.mkdirs();
}
File file = getUniqueFile(downloadsDir, filename);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(content.getBytes(StandardCharsets.UTF_8));
}
MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null);
}
private File getUniqueFile(File directory, String filename) {
File file = new File(directory, filename);
if (!file.exists()) {
return file;
}
// Add number suffix if file exists
String name = filename.substring(0, filename.lastIndexOf("."));
String extension = filename.substring(filename.lastIndexOf("."));
int counter = 1;
while (file.exists()) {
file = new File(directory, name + "(" + counter + ")" + extension);
counter++;
}
return file;
}
// Helper method to determine MIME type
private String getMimeType(String filename) {
String extension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
return switch (extension) {
case "txt" -> "text/plain";
case "pdf" -> "application/pdf";
case "json" -> "application/json";
case "csv" -> "text/csv";
case "html" -> "text/html";
case "jpg", "jpeg" -> "image/jpeg";
case "png" -> "image/png";
default -> "application/octet-stream";
};
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String endpoint = intent.getStringExtra("endpoint");
Log.i("CompassApp", "onNewIntent called with endpoint: " + endpoint);
if (endpoint != null) {
Log.i("CompassApp", "redirecting to endpoint: " + endpoint);
try {
String payload = new JSONObject().put("endpoint", endpoint).toString();
Log.i("CompassApp", "Handling notif click: " + payload);
bridge.getWebView().post(() -> bridge.getWebView().evaluateJavascript("handleAppLink(" + payload + ");", null));
} catch (JSONException e) {
Log.i("CompassApp", "Failed to encode JSON payload", e);
}
} else {
Uri data = intent.getData();
if (data != null) {
handleDeepLink(data.toString());
} else {
Log.i("CompassApp", "No relevant data");
}
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i("CompassApp", "onCreate called");
super.onCreate(savedInstanceState);
WebView webView = this.bridge.getWebView();
webView.setWebViewClient(new BridgeWebViewClient(this.bridge));
if (BuildConfig.ENABLE_WEBVIEW_DEBUG) {
WebView.setWebContentsDebuggingEnabled(true);
}
// Set a recognizable User-Agent (always reliable)
WebSettings settings = webView.getSettings();
settings.setUserAgentString(settings.getUserAgentString() + " CompassAppWebView");
settings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebAppInterface(this), "AndroidBridge");
registerPlugin(PushNotificationsPlugin.class);
// Initialize the Bridge with Push Notifications plugin
// this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
// add(com.getcapacitor.plugin.PushNotifications.class);
// }});
askNotificationPermission();
appUpdateManager = AppUpdateManagerFactory.create(this);
checkForUpdates();
Uri data = getIntent().getData();
if (data != null) {
pendingDeepLink = data.toString();
} else {
// Check for notification endpoint when app is opened from cold start via notification click
String endpoint = getIntent().getStringExtra("endpoint");
if (endpoint != null) {
Log.i("CompassApp", "onCreate found endpoint from notification: " + endpoint);
pendingDeepLink = endpoint;
}
}
}
private void handleDeepLink(String url) {
try {
String path = new URL(url).getPath();
String payload = new JSONObject().put("url", url).put("endpoint", path).toString();
Log.i("CompassApp", "Handling deep link: " + url);
bridge.getWebView().post(() -> bridge.getWebView().evaluateJavascript("handleAppLink(" + payload + ");", null));
} catch (Exception e) {
Log.e("CompassApp", "Failed to handle deep link for " + url, e);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode >= GoogleProvider.REQUEST_AUTHORIZE_GOOGLE_MIN && requestCode < GoogleProvider.REQUEST_AUTHORIZE_GOOGLE_MAX) {
PluginHandle pluginHandle = getBridge().getPlugin("SocialLogin");
if (pluginHandle == null) {
Log.i("CompassApp", "SocialLogin login handle is null");
return;
}
Plugin plugin = pluginHandle.getInstance();
if (!(plugin instanceof SocialLoginPlugin)) {
Log.i("CompassApp", "SocialLogin plugin instance is not SocialLoginPlugin");
return;
}
Log.i("CompassApp", "handleGoogleLoginIntent");
((SocialLoginPlugin) plugin).handleGoogleLoginIntent(requestCode, data);
}
}
// This function will never be called, leave it empty
@Override
public void IHaveModifiedTheMainActivityForTheUseWithSocialLoginPlugin() {
}
private static final int UPDATE_REQUEST_CODE = 500;
private AppUpdateManager appUpdateManager;
private static final String TAG = "MainActivity";
private void checkForUpdates() {
appUpdateManager.getAppUpdateInfo()
.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
startImmediateUpdate(appUpdateInfo);
} else if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
startFlexibleUpdate(appUpdateInfo);
}
}
})
.addOnFailureListener(exception -> {
// Handle error - log it
Log.e(TAG, "Failed to check For Updates", exception);
});
}
private void startImmediateUpdate(AppUpdateInfo appUpdateInfo) {
AppUpdateOptions options = AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build();
appUpdateManager.startUpdateFlow(appUpdateInfo, this, options)
.addOnSuccessListener(result -> {
Log.i(TAG, "Immediate update started successfully");
})
.addOnFailureListener(exception -> {
Log.e(TAG, "Failed to start immediate update", exception);
});
}
private void startFlexibleUpdate(AppUpdateInfo appUpdateInfo) {
AppUpdateOptions options = AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build();
appUpdateManager.startUpdateFlow(appUpdateInfo, this, options)
.addOnSuccessListener(result -> {
Log.i(TAG, "Flexible update started successfully");
})
.addOnFailureListener(exception -> {
Log.e(TAG, "Failed to start flexible update", exception);
});
}
@Override
public void onResume() {
super.onResume();
// Check if an immediate update was interrupted
appUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
startImmediateUpdate(appUpdateInfo);
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
appUpdateManager = null;
}
}

View File

@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">Compass</string>
<string name="title_activity_main">Compass</string>
<string name="package_name">com.compassconnections.app</string>
<string name="custom_url_scheme">com.compassconnections.app</string>
</resources>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowTranslucentStatus">true</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
</style>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>

View File

@@ -0,0 +1,18 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

29
android/build.gradle Normal file
View File

@@ -0,0 +1,29 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
classpath 'com.google.gms:google-services:4.4.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,21 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
include ':capacitor-keyboard'
project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
include ':capacitor-push-notifications'
project(':capacitor-push-notifications').projectDir = new File('../node_modules/@capacitor/push-notifications/android')
include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
include ':capawesome-capacitor-live-update'
project(':capawesome-capacitor-live-update').projectDir = new File('../node_modules/@capawesome/capacitor-live-update/android')
include ':capgo-capacitor-social-login'
project(':capgo-capacitor-social-login').projectDir = new File('../node_modules/@capgo/capacitor-social-login/android')

3
android/capawesome.json Normal file
View File

@@ -0,0 +1,3 @@
{
"version": 24
}

22
android/gradle.properties Normal file
View File

@@ -0,0 +1,22 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true

View File

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

252
android/gradlew vendored Executable file
View File

@@ -0,0 +1,252 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
android/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

5
android/settings.gradle Normal file
View File

@@ -0,0 +1,5 @@
include ':app'
include ':capacitor-cordova-android-plugins'
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
apply from: 'capacitor.settings.gradle'

16
android/variables.gradle Normal file
View File

@@ -0,0 +1,16 @@
ext {
minSdkVersion = 23
compileSdkVersion = 35
targetSdkVersion = 35
androidxActivityVersion = '1.9.2'
androidxAppCompatVersion = '1.7.0'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.15.0'
androidxFragmentVersion = '1.8.4'
coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.12.1'
junitVersion = '4.13.2'
androidxJunitVersion = '1.2.1'
androidxEspressoCoreVersion = '3.6.1'
cordovaAndroidVersion = '10.1.1'
}

View File

@@ -1,92 +0,0 @@
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { signOut, useSession } from "next-auth/react";
import { FaHome } from "react-icons/fa";
import ThemeToggle from "@/lib/client/theme";
export default function Header() {
const { data: session } = useSession();
const [isSmallScreen, setIsSmallScreen] = useState(false);
useEffect(() => {
const checkScreenSize = () => {
setIsSmallScreen(window.innerWidth < 640); // Tailwind's 'sm' breakpoint is 640px
};
// Initial check
checkScreenSize();
// Add event listener for window resize
window.addEventListener('resize', checkScreenSize);
// Clean up the event listener when the component unmounts
return () => window.removeEventListener('resize', checkScreenSize);
}, []);
const fontStyle = "transition px-2 py-2 text-sm font-medium xs:text-xs"
return (
<header className="w-full
{/*shadow-md*/}
py-4 px-8 xs:px-4">
<nav className="flex justify-between items-center">
<Link
href="/"
className="text-xl font-bold hover:text-blue-600 transition-colors flex items-center"
aria-label={isSmallScreen ? "Home" : "IntentionalBond"}
>
{isSmallScreen ? <FaHome className="w-5 h-5" /> : 'IntentionalBond'}
</Link>
<div className="flex items-center space-x-2">
<ThemeToggle/>
<div className="flex items-center space-x-2">
<Link
href="/learn-more"
className={`${fontStyle} bg-gray-200 dark:bg-gray-600 text-gray-800 dark:text-white rounded-lg hover:bg-gray-300 dark:hover:bg-gray-500`}
>
Learn More
</Link>
</div>
{session ? (
<>
<div className="flex items-center space-x-2">
<Link
href="/profile"
className={`${fontStyle} text-blue-600 dark:text-blue-100 hover:text-blue-800 dark:hover:text-blue-300`}
>
My Profile
</Link>
{/*<Link*/}
{/* href="/profiles"*/}
{/* className="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition"*/}
{/*>*/}
{/* Dashboard*/}
{/*</Link>*/}
<button
onClick={() => signOut({callbackUrl: "/"})}
className={`${fontStyle} bg-red-500 text-white rounded-lg hover:bg-red-600`}
>
Sign Out
</button>
</div>
</>
) : (
<>
<Link href="/login" className={`${fontStyle} bg-blue-500 text-white rounded-lg hover:bg-blue-600 `}>
Sign In
</Link>
<Link href="/register"
className={`${fontStyle} bg-blue-500 text-white rounded-lg hover:bg-blue-600`}>
Sign Up
</Link>
</>
)}
</div>
</nav>
</header>
);
}

View File

@@ -1,27 +0,0 @@
import NextAuth from "next-auth";
import {authOptions} from "@/lib/server/auth";
const authHandler = NextAuth(authOptions);
export {authHandler as GET, authHandler as POST};
declare module "next-auth" {
interface Session {
user: {
id: string;
name: string;
email: string;
image: string;
emailVerified?: Date | null;
};
}
interface User {
emailVerified?: Date | null;
}
}
declare module "next-auth/jwt" {
interface JWT {
id: string;
}
}

View File

@@ -1,69 +0,0 @@
import bcrypt from "bcryptjs";
import {NextResponse} from "next/server";
import {prisma} from "@/lib/server/prisma";
import {v4 as uuidv4} from 'uuid';
// Helper function to generate a verification token
const generateVerificationToken = () => {
return uuidv4();
};
export async function POST(req: Request) {
try {
const {email, password, name} = await req.json();
if (!email || !password) {
return NextResponse.json({error: "Email and password required"}, {status: 400});
}
const existingUser = await prisma.user.findUnique({where: {email}});
if (existingUser) {
return NextResponse.json({error: "Email already in use"}, {status: 400});
}
const hashedPassword = await bcrypt.hash(password, 10);
const verificationToken = generateVerificationToken();
const verificationTokenExpires = new Date();
verificationTokenExpires.setHours(verificationTokenExpires.getHours() + 24); // Token expires in 24 hours
// Create user with verification token
const user = await prisma.user.create({
data: {
email,
password: hashedPassword,
name,
emailVerified: null, // Will be set when email is verified
verificationToken,
verificationTokenExpires,
},
});
// Send verification email. TODO once we have a domain
// You can only send testing emails to your own email address.
// To send emails to other recipients, please verify a domain at resend.com/domains,
// and change the `from` address to an email using this domain.
// const verificationUrl = `${process.env.NEXTAUTH_URL}/api/auth/verify-email?token=${verificationToken}`;
// const emailHtml = await render(VerificationEmail({ url: verificationUrl }));
// try {
// let payload = {
// from: `BayesBond <${process.env.EMAIL_FROM!}>`,
// to: email,
// subject: 'Verify your email',
// html: emailHtml,
// };
// console.log(`Verification email: ${payload}`);
// await resend.emails.send(payload);
// } catch (emailError) {
// console.error('Failed to send verification email:', emailError);
// }
return NextResponse.json({
message: "User created. Please check your email to verify your account.",
userId: user.id
}, {status: 201});
} catch (error) {
console.error(error);
return NextResponse.json({error: "Internal Server Error"}, {status: 500});
}
}

View File

@@ -1,43 +0,0 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/server/prisma";
export async function GET(req: Request) {
try {
const { searchParams } = new URL(req.url);
const token = searchParams.get('token');
if (!token) {
return NextResponse.redirect(new URL('/auth/error?error=InvalidToken', req.url));
}
// Find user with this verification token
const user = await prisma.user.findFirst({
where: {
verificationToken: token,
verificationTokenExpires: {
gt: new Date(), // Check if token is not expired
},
},
});
if (!user) {
return NextResponse.redirect(new URL('/auth/error?error=InvalidOrExpiredToken', req.url));
}
// Update user as verified
await prisma.user.update({
where: { id: user.id },
data: {
emailVerified: new Date(),
verificationToken: null,
verificationTokenExpires: null,
},
});
// Redirect to success page
return NextResponse.redirect(new URL('/auth/verification-success', req.url));
} catch (error) {
console.error('Email verification error:', error);
return NextResponse.redirect(new URL('/auth/error?error=VerificationFailed', req.url));
}
}

View File

@@ -1,50 +0,0 @@
import {GetObjectCommand, S3Client} from "@aws-sdk/client-s3";
import {getSignedUrl} from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
export async function GET(
req: Request
) {
// console.log(req)
const {searchParams} = new URL(req.url);
const key = searchParams.get('key'); // get the key from query params
if (!key) {
return new Response('S3 download error', {
status: 500,
headers: {'Content-Type': 'application/json'},
});
}
try {
// Option 1: Generate a signed URL (client downloads directly from S3)
const signedUrl = await getSignedUrl(
s3,
new GetObjectCommand({
Bucket: process.env.AWS_S3_BUCKET_NAME!,
Key: key,
}),
{expiresIn: 300} // 5 minutes
);
return new Response(JSON.stringify({url: signedUrl}), {
status: 200,
headers: {'Content-Type': 'application/json'},
});
} catch (err) {
console.error("S3 download error:", err);
return new Response('S3 download error', {
status: 500,
headers: {'Content-Type': 'application/json'},
});
}
}

View File

@@ -1,60 +0,0 @@
import { prisma } from "@/lib/server/prisma";
import { NextResponse } from "next/server";
export async function GET() {
try {
// Get all interests from the database
const cacheStrategy = { swr: 60, ttl: 60, tags: ["interests"] };
const interests = await prisma.interest.findMany({
select: {
id: true,
name: true,
},
orderBy: {
name: 'asc'
},
cacheStrategy: cacheStrategy,
});
const coreValues = await prisma.value.findMany({
select: {
id: true,
name: true,
},
orderBy: {
name: 'asc'
},
cacheStrategy: cacheStrategy,
});
const causeAreas = await prisma.causeArea.findMany({
select: {
id: true,
name: true,
},
orderBy: {
name: 'asc'
},
cacheStrategy: cacheStrategy,
});
const connections = await prisma.connection.findMany({
select: {
id: true,
name: true,
},
orderBy: {
name: 'asc'
},
cacheStrategy: cacheStrategy,
});
return NextResponse.json({ interests, coreValues, causeAreas, connections });
} catch (error) {
console.error('Error fetching interests:', error);
return NextResponse.json(
{ error: "Failed to fetch interests" },
{ status: 500 }
);
}
}

View File

@@ -1,28 +0,0 @@
import {NextResponse} from "next/server";
import {getSession} from "@/lib/server/auth";
import {retrieveUser} from "@/lib/server/db-utils";
export async function GET() {
const session = await getSession();
console.log(`Session: ${session?.user?.name}`);
if (!session?.user?.id)
return new NextResponse(JSON.stringify({error: "User not found"}), {
status: 404,
headers: {"Content-Type": "application/json"},
});
const id = session.user.id;
const user = await retrieveUser(id);
if (!user) {
return new NextResponse(JSON.stringify({error: "User not found"}), {
status: 404,
headers: {"Content-Type": "application/json"},
});
}
return NextResponse.json(user);
}

View File

@@ -1,110 +0,0 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/server/prisma";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/server/auth";
import {retrieveUser} from "@/lib/server/db-utils";
// Handler for GET /api/profiles/[id]
export async function GET(
request: Request,
context: { params: Promise<{ id: string }> }
) {
try {
const params = await context.params;
const { id } = params;
const user = await retrieveUser(id)
// If user not found, return 404
if (!user) {
return new NextResponse(JSON.stringify({error: "User not found"}), {
status: 404,
headers: {"Content-Type": "application/json"},
});
}
return new NextResponse(JSON.stringify(user), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Error fetching user profile:", error);
return new NextResponse(
JSON.stringify({ error: "Failed to fetch user profile" }),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
}
// Handler for DELETE /api/profiles/[id]
export async function DELETE(
request: Request,
context: { params: Promise<{ id: string }> }
) {
try {
// Verify authentication
const session = await getServerSession(authOptions);
if (!session?.user) {
return new NextResponse(
JSON.stringify({ error: 'You must be signed in to delete a profile' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
);
}
const params = await context.params;
const { id } = params;
// Verify the user is trying to delete their own profile
if (session.user.id !== id) {
return new NextResponse(
JSON.stringify({ error: 'You can only delete your own profile' }),
{ status: 403, headers: { 'Content-Type': 'application/json' } }
);
}
// Delete related records first to avoid foreign key constraints
await prisma.$transaction([
// Delete prompt answers
prisma.promptAnswer.deleteMany({
where: { profileId: id },
}),
// Delete intellectual interests
prisma.profileInterest.deleteMany({
where: { profileId: id },
}),
prisma.profileValue.deleteMany({
where: { profileId: id },
}),
// Delete cause areas
prisma.profileCauseArea.deleteMany({
where: { profileId: id },
}),
// Delete desired connections
prisma.profileConnection.deleteMany({
where: { profileId: id },
}),
// Delete the profile
prisma.profile.deleteMany({
where: { id: id },
}),
// Finally, delete the user
prisma.user.delete({
where: { id },
}),
]);
return new NextResponse(
JSON.stringify({ success: true, message: 'Profile deleted successfully' }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
} catch (error) {
console.error('Error deleting profile:', error);
return new NextResponse(
JSON.stringify({ error: 'Failed to delete profile' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
}

View File

@@ -1,20 +0,0 @@
import { prisma } from "@/lib/server/prisma";
import { NextResponse } from "next/server";
export async function GET() {
try {
// Get the total count of users from the database
const count = await prisma.user.count();
return NextResponse.json({ count });
} catch (error) {
console.error('Error fetching user count:', error);
return NextResponse.json(
{ error: "Failed to fetch user count" },
{ status: 500 }
);
}
}
// This ensures the route is not cached
export const dynamic = 'force-dynamic';

View File

@@ -1,26 +0,0 @@
import { prisma } from "@/lib/server/prisma";
import { NextResponse } from "next/server";
export async function GET() {
try {
let data = await prisma.promptAnswer.findMany({
select: {
prompt: true,
},
distinct: ['prompt'],
});
const uniquePrompts = data.map((prompt) => prompt.prompt);
return NextResponse.json({ uniquePrompts });
} catch (error) {
console.error('Error fetching prompts:', error);
return NextResponse.json(
{ error: "Failed to fetch prompts" },
{ status: 500 }
);
}
}
// This ensures the route is not cached
export const dynamic = 'force-dynamic';

View File

@@ -1,270 +0,0 @@
import {prisma} from "@/lib/server/prisma";
import {NextResponse} from "next/server";
import {getSession} from "@/lib/server/auth";
export async function GET(request: Request) {
const url = new URL(request.url);
const page = parseInt(url.searchParams.get("page") || "1");
const gender = url.searchParams.get("gender");
const minAge = url.searchParams.get("minAge");
const maxAge = url.searchParams.get("maxAge");
const minIntroversion = url.searchParams.get("minIntroversion");
const maxIntroversion = url.searchParams.get("maxIntroversion");
const interests = url.searchParams.get("interests")?.split(",").filter(Boolean) || [];
const coreValues = url.searchParams.get("coreValues")?.split(",").filter(Boolean) || [];
const causeAreas = url.searchParams.get("causeAreas")?.split(",").filter(Boolean) || [];
const connections = url.searchParams.get("connections")?.split(",").filter(Boolean) || [];
const searchQuery = url.searchParams.get("search") || "";
const profilesPerPage = 100;
const offset = (page - 1) * profilesPerPage;
const session = await getSession();
console.log(`Session: ${session?.user?.name}`);
// Build the where clause based on filters
const where: any = {
id: {not: session?.user?.id},
};
where.profile = {};
where.profile.AND = [];
if (gender) {
where.profile = {
...where.profile,
gender: gender,
};
}
// Add age filtering
const currentYear = new Date().getFullYear();
if (minAge || maxAge) {
where.profile = {
...where.profile,
birthYear: {}
};
if (minAge) {
where.profile.birthYear.lte = currentYear - parseInt(minAge);
}
if (maxAge) {
where.profile.birthYear.gte = currentYear - parseInt(maxAge);
}
}
// Add introversion filtering (careful: the query value is actually extroversion
if (minIntroversion || maxIntroversion) {
where.profile = {
...where.profile,
introversion: {}
};
if (minIntroversion) {
where.profile.introversion.lte = 100 - parseInt(minIntroversion);
}
if (maxIntroversion) {
where.profile.introversion.gte = 100 - parseInt(maxIntroversion);
}
}
// OR
// if (interests.length > 0) {
// where.profile = {
// ...where.profile,
// intellectualInterests: {
// some: {
// interest: {
// name: {in: interests},
// },
// },
// },
// };
// }
// AND
if (interests.length > 0) {
where.profile.AND = [
...where.profile.AND,
...interests.map((name) => ({
intellectualInterests: {
some: {
interest: {
name: name,
},
},
},
})),
];
}
// AND
if (coreValues.length > 0) {
where.profile.AND = [
...where.profile.AND,
...coreValues.map((name) => ({
coreValues: {
some: {
value: {
name: name,
},
},
},
})),
];
}
if (causeAreas.length > 0) {
where.profile.AND = [
...where.profile.AND,
...causeAreas.map((name) => ({
causeAreas: {
some: {
causeArea: {
name: name,
},
},
},
})),
];
}
// OR
if (connections.length > 0) {
where.profile = {
...where.profile,
desiredConnections: {
some: {
connection: {
name: {in: connections},
},
},
},
};
}
if (searchQuery) {
where.OR = [
{name: {contains: searchQuery, mode: 'insensitive'}},
// {email: {contains: searchQuery, mode: 'insensitive'}},
{
profile: {
description: {contains: searchQuery, mode: 'insensitive'},
},
},
{
profile: {
occupation: {contains: searchQuery, mode: 'insensitive'},
},
},
{
profile: {
location: {contains: searchQuery, mode: 'insensitive'},
},
},
{
profile: {
contactInfo: {contains: searchQuery, mode: 'insensitive'},
},
},
{
profile: {
intellectualInterests: {
some: {
interest: {
name: {contains: searchQuery, mode: "insensitive"},
},
},
},
},
},
{
profile: {
coreValues: {
some: {
value: {
name: {contains: searchQuery, mode: "insensitive"},
},
},
},
},
},
{
profile: {
causeAreas: {
some: {
causeArea: {
name: {contains: searchQuery, mode: "insensitive"},
},
},
},
},
},
{
profile: {
desiredConnections: {
some: {
connection: {
name: {contains: searchQuery, mode: "insensitive"},
},
},
},
},
},
{
profile: {
promptAnswers: {
some: {
answer: {contains: searchQuery, mode: "insensitive"},
},
},
},
},
{
profile: {
promptAnswers: {
some: {
prompt: {contains: searchQuery, mode: "insensitive"},
},
},
},
},
];
}
console.log(where.profile);
// Fetch paginated and filtered profiles
const cacheStrategy = {swr: 60, ttl: 60, tags: ["profiles"]};
const profiles = await prisma.user.findMany({
skip: offset,
take: profilesPerPage,
orderBy: {createdAt: "desc"},
where,
select: {
id: true,
name: true,
// email: true,
image: true,
createdAt: true,
profile: {
include: {
intellectualInterests: {include: {interest: true}},
coreValues: {include: {value: true}},
causeAreas: {include: {causeArea: true}},
desiredConnections: {include: {connection: true}},
promptAnswers: true,
},
},
},
cacheStrategy: cacheStrategy,
});
const totalProfiles = await prisma.user.count();
const totalPages = Math.ceil(totalProfiles / profilesPerPage);
console.log({profiles, totalPages});
return NextResponse.json({profiles, totalPages});
}

View File

@@ -1,75 +0,0 @@
import {NextResponse} from 'next/server';
import {getSession} from '@/lib/server/auth';
import {v4 as uuidv4} from 'uuid';
import {GetObjectCommand, PutObjectCommand, S3Client} from '@aws-sdk/client-s3';
import {getSignedUrl} from "@aws-sdk/s3-request-presigner";
const s3Client = new S3Client({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
export async function POST(request: Request) {
try {
const session = await getSession();
const userId = session?.user?.id;
if (!userId) {
return NextResponse.json({error: 'Not authenticated'}, {status: 401});
}
const formData = await request.formData();
console.log('formData', formData);
const file = formData.get('file') as File | null;
if (!file) return NextResponse.json({error: 'No file provided'}, {status: 400});
// Validate file type
if (!file.type.startsWith('image/')) {
return NextResponse.json({error: 'Only image files are allowed'}, {status: 400});
}
// Validate file size (10MB max)
if (file.size > 10 * 1024 * 1024) {
return NextResponse.json({error: 'File size must be less than 10MB'}, {status: 400});
}
const fileExtension = file.name.split('.').pop();
const fileName = `${uuidv4()}.${fileExtension}`;
const fileBuffer = await file.arrayBuffer();
const key = `profile-pictures/${userId}/${fileName}`;
const uploadParams = {
Bucket: process.env.AWS_S3_BUCKET_NAME!,
Key: key,
Body: Buffer.from(fileBuffer),
ContentType: file.type,
};
const response = await s3Client.send(new PutObjectCommand(uploadParams));
console.log(`Response: ${response}`);
// get signed url
const url = await getSignedUrl(
s3Client,
new GetObjectCommand({
Bucket: process.env.AWS_S3_BUCKET_NAME!,
Key: key,
}),
{expiresIn: 300} // 5 minutes
);
console.log(`Signed URL: ${url}`);
// const fileUrl = `${process.env.AWS_S3_BUCKET_NAME}/profile-pictures/${fileName}`;
return NextResponse.json({url: url, key: key});
} catch (error) {
console.error('Upload error:', error);
return NextResponse.json(
{error: 'Failed to upload file'},
{status: 500}
);
}
}

View File

@@ -1,132 +0,0 @@
import {NextResponse} from "next/server";
import {prisma} from "@/lib/server/prisma";
import {getSession} from "@/lib/server/auth";
export async function POST(req: Request) {
try {
const session = await getSession();
if (!session?.user?.email) {
return NextResponse.json(
{error: "Not authenticated"},
{status: 401}
);
}
const data = await req.json();
const {profile, image, name, interests = [], connections = [], coreValues = [], causeAreas = []} = data;
Object.keys(profile).forEach(key => {
if (profile[key] === '' || !profile[key]) {
delete profile[key];
}
});
console.log('profile', profile);
// Start a transaction to ensure data consistency
const result = await prisma.$transaction(async (prisma) => {
if (profile.promptAnswers) {
const profileData = await prisma.profile.findUnique({
where: {
userId: session.user.id,
},
});
console.log('profileData:', profileData);
const profileId = profileData?.id;
if (profileId) {
const deleted = await prisma.promptAnswer.deleteMany({
where: {
profileId: profileData?.id,
},
});
console.log('Deleted prompt answers:', deleted);
}
}
// First, update/create the profile
const updatedUser = await prisma.user.update({
where: {email: session.user.email},
data: {
...(image && {image}),
...(name && {name}),
profile: {
upsert: {
create: profile,
update: profile,
},
},
},
include: {
profile: true,
},
});
const modelMap: any = {
interest: prisma.interest,
profileInterest: prisma.profileInterest,
connection: prisma.connection,
profileConnection: prisma.profileConnection,
value: prisma.value,
profileValue: prisma.profileValue,
causeArea: prisma.causeArea,
profileCauseArea: prisma.profileCauseArea,
} as const;
type ModelKey = keyof typeof modelMap;
async function handleFeatures(features: any, attribute: ModelKey, profileAttribute: string, idName: string) {
// Add new features
if (features.length > 0 && updatedUser.profile) {
// First, find or create all features
console.log('profile', profileAttribute, profileAttribute);
const operations = features.map((feat: { id?: string; name: string }) =>
modelMap[attribute].upsert({
where: {id: feat.id || ''},
update: {name: feat.name},
create: {name: feat.name},
})
);
const createdFeatures = await Promise.all(operations);
// Get the IDs of all created/updated features
const ids = createdFeatures.map(v => v.id);
// First, remove all existing interests for this profile
await modelMap[profileAttribute].deleteMany({
where: {profileId: updatedUser.profile.id},
});
// Then, create new connections
if (ids.length > 0) {
await modelMap[profileAttribute].createMany({
data: ids.map(id => ({
profileId: updatedUser.profile!.id,
[idName]: id,
})),
skipDuplicates: true,
});
}
}
}
await handleFeatures(interests, 'interest', 'profileInterest', 'interestId')
await handleFeatures(connections, 'connection', 'profileConnection', 'connectionId')
await handleFeatures(coreValues, 'value', 'profileValue', 'valueId')
await handleFeatures(causeAreas, 'causeArea', 'profileCauseArea', 'causeAreaId')
return updatedUser
});
return NextResponse.json(result);
} catch (error) {
console.error('Profile update error:', error);
return NextResponse.json(
{error: "Failed to update profile"},
{status: 500}
);
}
}

View File

@@ -1,66 +0,0 @@
"use client";
export default function AuthError() {
}
// import Link from "next/link";
//
// export default function AuthError(
// searchParams?: { [key: string]: string | string[] | undefined }
// ) {
// const error = searchParams?.error;
// const errorMessage = (() => {
// switch (error) {
// case "InvalidToken":
// return "The verification link is invalid.";
// case "InvalidOrExpiredToken":
// return "The verification link is invalid or has expired. Please request a new one.";
// case "VerificationFailed":
// return "Email verification failed. Please try again later.";
// default:
// return "An unexpected error occurred. Please try again.";
// }
// })();
//
// return (
// <div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
// <div className="max-w-md w-full space-y-8 text-center">
// <div className="rounded-full bg-red-100 p-3 inline-flex items-center justify-center">
// <svg
// className="h-12 w-12 text-red-600"
// fill="none"
// viewBox="0 0 24 24"
// stroke="currentColor"
// >
// <path
// strokeLinecap="round"
// strokeLinejoin="round"
// strokeWidth={2}
// d="M6 18L18 6M6 6l12 12"
// />
// </svg>
// </div>
// <h2 className="mt-6 text-3xl font-extrabold ">
// Verification Error
// </h2>
// <p className="mt-2 text-sm text-gray-600">{errorMessage}</p>
// <div className="mt-6 space-y-4">
// <Link
// href="/register"
// className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
// >
// Back to Registration
// </Link>
// <Link
// href="/login"
// className="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover: focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
// >
// Go to Login
// </Link>
// </div>
// </div>
// </div>
// );
// }

View File

@@ -1,39 +0,0 @@
import Link from "next/link";
export default function VerificationSuccess() {
return (
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8 text-center">
<div className="rounded-full bg-green-100 p-3 inline-flex items-center justify-center">
<svg
className="h-12 w-12 text-green-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
</div>
<h2 className="mt-6 text-3xl font-extrabold ">
Email Verified Successfully!
</h2>
<p className="mt-2 text-sm text-gray-600">
Your email has been successfully verified. You can now log in to your account.
</p>
<div className="mt-6">
<Link
href="/login"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Go to Login
</Link>
</div>
</div>
</div>
);
}

View File

@@ -1,994 +0,0 @@
'use client';
import {ChangeEvent, ReactNode, Suspense, useEffect, useRef, useState} from 'react';
import {useRouter, useSearchParams} from 'next/navigation';
import {signOut, useSession} from 'next-auth/react';
import Image from 'next/image';
import {ConflictStyle, Gender, PersonalityType} from "@prisma/client";
import {parseImage} from "@/lib/client/media";
import {DeleteProfileButton} from "@/lib/client/profile";
import PromptAnswer from '@/components/ui/PromptAnswer';
import imageCompression from 'browser-image-compression';
export default function CompleteProfile() {
return (
<Suspense fallback={<div></div>}>
<RegisterComponent/>
</Suspense>
);
}
interface Prompt {
prompt: string
answer: string
}
function RegisterComponent() {
const searchParams = useSearchParams();
const redirect = searchParams.get('redirect') || '/';
const [description, setDescription] = useState('');
const [contactInfo, setContactInfo] = useState('');
const [location, setLocation] = useState('');
const [name, setName] = useState('');
const [gender, setGender] = useState('');
const [promptOptions, setPromptOptions] = useState([]);
const [age, setAge] = useState<number | null>(null);
const [introversion, setIntroversion] = useState<number | null>(null);
const [personalityType, setPersonalityType] = useState('');
const [conflictStyle, setConflictStyle] = useState('');
const [promptAnswers, setPromptAnswers] = useState<any>({});
const [image, setImage] = useState<string>('');
const [key, setKey] = useState<string>('');
const [images, setImages] = useState<string[]>([]);
const [keys, setKeys] = useState<string[]>([]);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(true);
const fileInputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
const {data: session, update} = useSession();
const hooks = Object.fromEntries(['interests', 'coreValues', 'description', 'connections', 'causeAreas'].map((id) => {
const [showMoreInfo, setShowMoreInfo] = useState(false);
const [newFeature, setNewFeature] = useState('');
const [allFeatures, setAllFeatures] = useState<{ id: string, name: string }[]>([]);
const [selectedFeatures, setSelectedFeatures] = useState<Set<string>>(new Set());
const dropdownRef = useRef<HTMLDivElement>(null);
const [showDropdown, setShowDropdown] = useState(false);
return [id, {
showMoreInfo,
setShowMoreInfo,
newFeature,
setNewFeature,
allFeatures,
setAllFeatures,
selectedFeatures,
setSelectedFeatures,
dropdownRef,
showDropdown,
setShowDropdown
}]
}));
const id = session?.user.id
// console.log(session)
// Fetch user profile data
useEffect(() => {
async function fetchUserProfile() {
if (!session?.user?.email) return;
try {
const response = await fetch('/api/profile');
if (response.ok) {
const userData = await response.json();
await parseImage(userData.image, setImage);
setName(userData.name || '');
if (userData?.profile) {
const {profile} = userData;
setDescription(profile.description || '');
setContactInfo(profile.contactInfo || '');
setLocation(profile.location || '');
setGender(profile.gender || '');
setPersonalityType(profile.personalityType || null);
setConflictStyle(profile.conflictStyle || '');
if (profile.promptAnswers) {
setPromptAnswers(Object.fromEntries(profile.promptAnswers.map((item: Prompt) => [item.prompt, item.answer])));
}
setIntroversion(profile.introversion || null);
if (profile.birthYear) {
setAge(new Date().getFullYear() - profile.birthYear);
}
// Set selected interests if any
function setSelectedFeatures(id: string, attribute: string, subAttribute: string) {
const feature = profile[attribute];
if (feature?.length > 0) {
const ids = feature.map((pi: any) => pi[subAttribute].id);
hooks[id].setSelectedFeatures(new Set(ids));
}
}
setSelectedFeatures('interests', 'intellectualInterests', 'interest')
setSelectedFeatures('coreValues', 'coreValues', 'value')
setSelectedFeatures('connections', 'desiredConnections', 'connection')
setSelectedFeatures('causeAreas', 'causeAreas', 'causeArea')
setImages([])
setKeys(profile?.images)
await Promise.all(
(profile?.images || []).map(async (img: string) => {
await parseImage(img, setImages, true);
})
);
}
}
} catch (error) {
console.error('Error fetching user profile:', error);
} finally {
setIsLoading(false);
}
}
fetchUserProfile();
}, [session]);
useEffect(() => {
async function asyncRun() {
try {
const res = await fetch('/api/profiles/prompts');
if (res.ok) {
const data = await res.json();
// console.log('uniquePrompts', data.uniquePrompts);
setPromptOptions(data.uniquePrompts);
}
} catch (error) {
console.error('Error from /api/prompts:', error);
}
}
asyncRun();
}, []);
// Load existing interests and set up click-outside handler
useEffect(() => {
async function fetchFeatures() {
try {
const res = await fetch('/api/interests');
if (res.ok) {
const data = await res.json();
for (const id of ['interests', 'coreValues', 'connections', 'causeAreas']) {
hooks[id].setAllFeatures(data[id] || []);
// Close dropdown when clicking outside
const handleClickOutside = (event: MouseEvent) => {
const hook = hooks[id];
const current = hook.dropdownRef.current;
if (current && !current.contains(event.target as Node)) {
hook.setShowDropdown(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
// return () => {
// document.removeEventListener('mousedown', handleClickOutside);
// };
}
}
} catch (error) {
console.error('Error loading' + id, error);
}
}
fetchFeatures();
}, []);
if (isLoading) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
);
}
const handleImagesUpload = async (e: ChangeEvent<HTMLInputElement>) => {
return handleImageUpload(e, false);
}
const handleImageUpload = async (e: ChangeEvent<HTMLInputElement>, headShot = true) => {
const file = e.target.files?.[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
setError('Please upload an image file');
return;
}
// Compression options
const options = {
maxSizeMB: 1, // Target max size in MB
maxWidthOrHeight: 1024, // Resize to fit within this dimension
useWebWorker: true,
};
try {
setIsUploading(true);
setError('');
const compressedFile = await imageCompression(file, options);
console.log(`Original: ${file.size / 1024} KB`);
console.log(`Compressed: ${compressedFile.size / 1024} KB`);
// Send via FormData
const formData = new FormData();
formData.append('file', compressedFile);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
console.log('response:', response.headers);
if (!response.ok) {
const errorData = await response.json();
setError(errorData.error || 'Failed to upload image');
return;
}
const {url, key} = await response.json();
if (headShot) {
setImage(url);
setKey(key);
console.log('headshot', key, url)
} else {
setImages(prev => [...prev, url]);
setKeys(prev => [...prev, key]);
}
// console.log(url, key);
console.log('image', key);
console.log('images', keys);
} catch
(error) {
console.error('Upload error:', error);
setError(error instanceof Error ? error.message : 'Failed to upload image');
} finally {
setIsUploading(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!session?.user?.email) return;
try {
setIsSubmitting(true);
setError('');
const promptAnswersList = Object.entries(promptAnswers).map(([prompt, answer]) => ({prompt, answer}));
console.log('promptAnswersList', promptAnswersList)
console.log('submit image', key);
console.log('submit images', keys);
const data: any = {
profile: {
description,
contactInfo,
location,
gender: gender as Gender,
...(age && {birthYear: new Date().getFullYear() - age}),
introversion,
personalityType: personalityType as PersonalityType,
conflictStyle: conflictStyle as ConflictStyle,
promptAnswers: {create: promptAnswersList},
images: keys,
},
...(key && {image: key}),
...(name && {name}),
};
for (const name of ['interests', 'connections', 'coreValues', 'causeAreas']) {
data[name] = Array.from(hooks[name].selectedFeatures).map(id => ({
id: id.startsWith('new-') ? undefined : id,
name: hooks[name].allFeatures.find(i => i.id === id)?.name || id.replace('new-', '')
}));
}
console.log('data', data)
const response = await fetch('/api/user/update-profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
const errorData = await response.json();
setError(errorData.error || 'Failed to update profile');
return;
}
await update();
router.push(redirect);
} catch (error) {
console.error('Profile update error:', error);
setError(error instanceof Error ? error.message : 'Failed to update profile');
} finally {
setIsSubmitting(false);
}
};
const genderOptions = Object.values(Gender);
// const personalityOptions = Object.values(PersonalityType);
const conflictOptions = Object.values(ConflictStyle);
const headingStyle = "block text-base font-medium text-gray-700 dark:text-white mb-1";
function getDetails(id: string, brief: string, text: ReactNode) {
const hook = hooks[id];
const showMoreInfo = hook.showMoreInfo;
const setShowMoreInfo = hook.setShowMoreInfo;
return <>
<div className="mt-2 mb-4">
<button
type="button"
onClick={() => setShowMoreInfo(!showMoreInfo)}
className="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 flex items-center"
>
{showMoreInfo ? 'Hide info' : brief}
<svg
className={`w-4 h-4 ml-1 transition-transform ${showMoreInfo ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7"/>
</svg>
</button>
{showMoreInfo && (
<div className="mt-2 p-3 bg-gray-50 dark:bg-gray-800 rounded-md text-sm text-gray-700 dark:text-gray-300">
{text}
</div>
)}
</div>
</>;
}
interface DropdownConfig {
id: string;
title: string;
allowAdd: boolean;
content: ReactNode;
}
const dropdownConfig: DropdownConfig[] = [
{
id: 'connections', title: 'Desired Connections', allowAdd: false,
content: null
},
{
id: 'coreValues', title: 'Core Values', allowAdd: true,
content: <>
<p className="mt-2">
When defining your core values on a platform meant for forming deep, lasting bonds, focus on what governs your
choices, shapes your relationships, and anchors your sense of integrityeven when it's inconvenient. These
aren't traits you aspire to signal; theyre principles you consistently return to when life is uncertain or
difficult. Think in terms of how you treat others (e.g. intellectual honesty, compassion, loyalty), how you
approach truth (e.g. humility, curiosity, critical thinking), and how you handle conflict or complexity (e.g.
courage, nuance, responsibility). Be specific and truthfulavoid vague terms like kindness unless you can
explain what it actually looks like in practice. The point isn't to be agreeable to everyone, but to be
legible to those who share or deeply respect the values that define you. That clarity is what builds trust—and
trust is the foundation of any bond worth keeping.
</p>
</>
},
{
id: 'interests', title: 'Core Interests', allowAdd: true,
content: <>
<p className="mt-2">
When selecting your core interests on a platform designed to foster deep, lasting
bonds, think beyond surface-level hobbies and focus on what truly shapes how you see the world and
connect with others. Choose interests that reveal how you think, what you care about deeply, and
where youre most engaged—intellectually, emotionally, or ethically. These might include long-term
fascinations (like moral philosophy, storytelling, or systems thinking), enduring questions you
wrestle with, or areas where you're actively growing. Dont be afraid to show complexity or
contradiction; honesty invites resonance. The goal isnt to impress, but to be understoodby the
kind of person who wants to know you for who you actually are, not just what you do for fun.
</p>
</>
},
{
id: 'causeAreas', title: 'Cause Areas', allowAdd: true,
content: <>
<p className="mt-2">
When choosing your cause areas on a platform designed for deep, lasting connection, focus on the issues that
you feel personally compelled to engage withnot just whats socially approved or intellectually interesting.
Good cause areas reveal what breaks your heart, what energizes your long-term thinking, or what youd
willingly struggle for even if no one noticed. Be honest about what genuinely matters to you: that might be
existential risk reduction, prison abolition, mental health reform, animal welfare, epistemic integrity, or
education equity. You dont need to be an expert or activist to list a causejust sincerely invested. The
point isnt to posture but to expose what kind of future you want to help shape, and to find others whose
moral intuitions and sense of responsibility resonate with your own. That kind of alignment creates not just
shared goals, but durable trust.
</p>
</>
},
]
function getDropdown({id, title, allowAdd, content}: DropdownConfig) {
const hook = hooks[id];
const newFeature = hook.newFeature;
const setNewFeature = hook.setNewFeature;
const showDropdown = hook.showDropdown;
const setShowDropdown = hook.setShowDropdown;
const allFeatures = hook.allFeatures;
const setSelectedFeatures = hook.setSelectedFeatures;
const setAllFeatures = hook.setAllFeatures;
const dropdownRef = hook.dropdownRef;
const selectedFeatures = hook.selectedFeatures;
const toggleFeature = (featureId: string) => {
setSelectedFeatures(prev => {
const newSet = new Set(prev);
if (newSet.has(featureId)) {
newSet.delete(featureId);
} else {
newSet.add(featureId);
}
return newSet;
});
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
addNewFeature();
} else if (e.key === 'Escape') {
setShowDropdown(false);
}
};
const addNewFeature = (e?: React.FormEvent) => {
if (e) e.preventDefault();
const toAdd = newFeature.trim();
if (!toAdd) return;
// Check if interest already exists (case-insensitive)
const existingFeature = allFeatures.find(
i => i.name.toLowerCase() === toAdd.toLowerCase()
);
if (existingFeature) {
// Toggle selection if it exists
toggleFeature(existingFeature.id);
} else {
// Add new feature
const newObj = {id: `new-${Date.now()}`, name: toAdd};
setAllFeatures(prev => [...prev, newObj]);
setSelectedFeatures(prev => new Set(prev).add(newObj.id));
}
setNewFeature('');
setShowDropdown(false);
};
return <>
<div className="relative" ref={dropdownRef}>
<label className={headingStyle}>
{title}
</label>
{content && getDetails(id, 'Guidance', content)}
<div className="relative">
<div className="flex items-center border border-gray-300 rounded-md shadow-sm">
<input
type="text"
value={newFeature}
maxLength={100}
onChange={(e) => setNewFeature(e.target.value)}
onFocus={() => setShowDropdown(true)}
onKeyDown={handleKeyDown}
className="flex-1 min-w-0 block w-full px-3 py-2 rounded-l-md border-0 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder={allowAdd ? "Type to search or add" : "Type to search"}
/>
<button
type="button"
onClick={() => setShowDropdown(!showDropdown)}
className="px-3 py-2 border-l border-gray-300 text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 "
>
<svg className="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"/>
</svg>
</button>
{allowAdd &&
<button
type="button"
onClick={addNewFeature}
disabled={!newFeature.trim()}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-r-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
Add
</button>
}
</div>
{(showDropdown || newFeature) && (
<div
className="absolute z-10 mt-1 w-full bg-white dark:bg-gray-800 shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
{/* New interest option */}
{allowAdd && newFeature && !allFeatures.some(i =>
i.name.toLowerCase() === newFeature.toLowerCase()
) && (
<div
className=" cursor-default select-none relative py-2 pl-3 pr-9 hover:bg-blue-50 dark:hover:bg-gray-700"
onClick={() => addNewFeature()}
>
<div className="flex items-center">
<span className="font-normal ml-3 block truncate">
Add "{newFeature}"
</span>
</div>
</div>
)}
{/* Filtered interests */}
{allFeatures
.filter(interest =>
interest.name.toLowerCase().includes(newFeature.toLowerCase())
)
.map((interest) => (
<div
key={interest.id}
className="cursor-default select-none relative py-2 pl-3 pr-9 hover:bg-blue-50 dark:hover:bg-gray-700"
onClick={() => {
toggleFeature(interest.id);
setNewFeature('');
}}
>
<div className="flex items-center">
<input
type="checkbox"
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded "
checked={selectedFeatures.has(interest.id)}
onChange={(e) => {
e.stopPropagation()
}}
/>
<span className="font-normal ml-3 block truncate">{interest.name}</span>
</div>
</div>
))}
</div>
)}
</div>
{/* Selected interests */}
<div className="flex flex-wrap gap-2 mt-3">
{Array.from(selectedFeatures).map(featureId => {
const interest = allFeatures.find(i => i.id === featureId);
if (!interest) return null;
return (
<span
key={featureId}
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 dark:text-white dark:bg-gray-700"
>
{interest.name}
<button
type="button"
onClick={(e) => {
e.stopPropagation();
toggleFeature(featureId);
}}
className="ml-1.5 inline-flex items-center justify-center h-4 w-4 rounded-full bg-blue-200 dark:bg-gray-500 hover:bg-blue-300 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<span className="sr-only">Remove {interest.name}</span>
<svg className="h-2 w-2" fill="currentColor" viewBox="0 0 8 8">
<path
d="M4 3.293L6.646.646a.5.5 0 01.708.708L4.707 4l2.647 2.646a.5.5 0 01-.708.708L4 4.707l-2.646 2.647a.5.5 0 01-.708-.708L3.293 4 .646 1.354a.5.5 0 01.708-.708L4 3.293z"/>
</svg>
</button>
</span>
);
})}
</div>
</div>
</>
}
function errorBlock() {
return <div className="bg-red-50 border-l-4 border-red-400 p-4">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"/>
</svg>
</div>
<div className="ml-3">
<p className="text-sm text-red-700">{error}</p>
</div>
</div>
</div>
}
return (
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-3xl w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold ">
Complete Your Profile
</h2>
</div>
{error && errorBlock()}
<div className="flex justify-center mb-6">
<div className="relative">
<div className="h-32 w-32 rounded-full overflow-hidden border-4 border-white shadow-md">
{image ? (
<Image
src={image}
alt="Profile"
width={128}
height={128}
className="h-full w-full object-cover"
/>
) : (
<div className="h-full w-full bg-gray-200 flex items-center justify-center">
<span className="text-4xl text-gray-500">
{session?.user?.name?.charAt(0).toUpperCase() || 'U'}
</span>
</div>
)}
</div>
<label
className="absolute -bottom-2 -right-2 bg-blue-600 text-white rounded-full p-2 cursor-pointer hover:bg-blue-700 transition-colors"
title="Upload photo"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd"
d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z"
clipRule="evenodd"/>
</svg>
<input
type="file"
ref={fileInputRef}
onChange={handleImageUpload}
accept="image/*"
className="hidden"
disabled={isUploading}
/>
</label>
</div>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div>
<label htmlFor="name" className={headingStyle}>
Name
</label>
<input
id="name"
name="name"
type="text"
value={name}
maxLength={100}
onChange={(e) => setName(e.target.value)}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
// placeholder=""
/>
</div>
<div className="space-y-4">
<div>
<label htmlFor="gender" className={headingStyle}>
Gender
</label>
<select
id="gender"
name="gender"
// required
value={gender || ''}
onChange={(e) => setGender(e.target.value as Gender)}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
>
<option value="">Select your gender</option>
{genderOptions.map((g) => (
<option key={g} value={g}>
{g.replace(/_/g, ' ')}
</option>
))}
</select>
</div>
<div>
<label htmlFor="age" className={headingStyle}>
Age
</label>
<input
id="age"
name="age"
type="number"
min="15"
max="100"
value={age ?? ''}
onChange={(e) => setAge(Number(e.target.value))}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
// placeholder=""
/>
</div>
<div>
<label htmlFor="location" className={headingStyle}>
Location
</label>
<input
id="location"
name="location"
type="text"
value={location}
maxLength={100}
onChange={(e) => setLocation(e.target.value)}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="City, Country"
/>
</div>
{getDropdown(dropdownConfig[0])}
{getDropdown(dropdownConfig[1])}
{getDropdown(dropdownConfig[2])}
{getDropdown(dropdownConfig[3])}
<div>
<label htmlFor="introversion" className={headingStyle}>
Introversion (0) - Extroversion (100)
</label>
<input
id="introversion"
name="introversion"
type="number"
min="0"
max="100"
value={introversion ? 100 - introversion : ''}
onChange={(e) => setIntroversion(100 - Number(e.target.value))}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
// placeholder=""
/>
</div>
{/*<div>*/}
{/* <label htmlFor="personalityType" className={headingStyle}>*/}
{/* Personality Type*/}
{/* </label>*/}
{/* <select*/}
{/* id="personalityType"*/}
{/* name="personalityType"*/}
{/* value={personalityType || ''}*/}
{/* onChange={(e) => setPersonalityType(e.target.value as PersonalityType)}*/}
{/* className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"*/}
{/* >*/}
{/* <option value="">Select your personality type</option>*/}
{/* {personalityOptions.map((type) => (*/}
{/* <option key={type} value={type}>*/}
{/* {type}*/}
{/* </option>*/}
{/* ))}*/}
{/* </select>*/}
{/*</div>*/}
<div>
<label htmlFor="conflictStyle" className={headingStyle}>
Conflict Style
</label>
<select
id="conflictStyle"
name="conflictStyle"
value={conflictStyle || ''}
onChange={(e) => setConflictStyle(e.target.value as ConflictStyle)}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
>
<option value="">Select your conflict style</option>
{conflictOptions.map((style) => (
<option key={style} value={style}>
{style}
</option>
))}
</select>
</div>
<div className="max-w-3xl w-full">
<label htmlFor="description" className={headingStyle}>
About You
<p className="text-sm italic">
Feel free to include any relevant links (dating / friends docs, etc.), but consider copy-pasting
the content here so that people can find you by keyword search.
</p>
</label>
{getDetails(
'description',
'Guidance',
<>
<p className="mt-2">To the extent that you are comfortable sharing, consider writing about:</p>
<ul className="list-disc pl-5 space-y-1">
<li>Your interests and what you're looking for: type of connection, activities to do, etc.</li>
<li>Your availability and timezone</li>
<li>What makes you unique</li>
<li>Your expectations and boundaries</li>
<li>Your intellectual interests (currently exploring, favorite, and least favorite)</li>
<li>Your core values</li>
<li>Your altruistic values: community engagement, social justice, and other cause areas</li>
<li>Your level of education, hobbies, pets, habits, subcultures, diet, emotional sensitivity, sense
of humor, ambition, organization, pet peeves, non-negotiables
</li>
<li>Your thinking style, results from evidence-based personality tests (e.g., Big 5)</li>
<li>Your physical and mental health: some traits that rub people the wrong way, triggers, therapy,
or what you are trying to improve
</li>
<li>If interested in romantic relationships, your love languages (giving and receiving), timeline,
romantic orientation, family projects, work-life balance, financial goals / habits, career goals,
housing situation (renting vs owning), and whether you would date someone who already has kids
</li>
<li>What you would like in your ideal person or connection—where they should be similar or different
from your own description
</li>
<li>Conversation starters or questions</li>
</ul>
</>
)
}
<textarea
id="description"
name="description"
rows={20}
// required
value={description}
maxLength={30000}
onChange={(e) => setDescription(e.target.value)}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Tell us about yourself, your background, and what you're looking for."
/>
</div>
<div>
<label htmlFor="contactInfo" className={headingStyle}>
Contact Information
</label>
<textarea
id="contactInfo"
name="contactInfo"
rows={5}
value={contactInfo}
maxLength={5000}
onChange={(e) => setContactInfo(e.target.value)}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="How can people reach you? (Email, social media, phone, Google Forms, etc.)"
/>
</div>
</div>
<div>
<label htmlFor="contactInfo" className={headingStyle}>
Prompts
</label>
<p className="text-sm italic">
As of now, you can only answer 1 prompt at a time—just save your profile after answering each prompt.
</p>
<PromptAnswer
prompts={promptOptions}
initialValues={promptAnswers}
onAnswerChange={(e) => {
console.log(e.promptId, e.prompt, e.text);
if (!e.prompt) return;
promptAnswers[e.prompt] = e.text;
setPromptAnswers(promptAnswers);
console.log(promptAnswers);
}}
/>
</div>
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-900 mb-3">Photos</h3>
<div className="grid grid-cols-3 gap-4">
{Array.from(new Set(images)).map((img, index) => (
<div key={index}
className="relative group aspect-square rounded-lg overflow-hidden border border-gray-200">
<Image
src={img}
alt={`Uploaded image ${index + 1}`}
width={200}
height={200}
className="h-full w-full object-cover"
/>
<button
type="button"
onClick={() => {
setImages(prev => prev.filter((_, i) => i !== index));
setKeys(prev => prev.filter((_, i) => i !== index));
}}
className="absolute top-2 right-2 bg-red-500 text-white rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity"
title="Remove image"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"/>
</svg>
</button>
</div>
))}
{Array.from(new Set(images)).length < 9 && (
<label
className="aspect-square flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:border-blue-500 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 text-gray-400 mb-1" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
{/*<span className="text-sm text-gray-500">*/}
{/* {images.length === 0 ? 'Add photos' : 'Add more'}*/}
{/*</span>*/}
<span className="text-xs text-gray-400">
{9 - Array.from(new Set(images)).length} remaining
</span>
<input
type="file"
ref={fileInputRef}
onChange={handleImagesUpload}
accept="image/*"
className="hidden"
disabled={isUploading || Array.from(new Set(images)).length >= 9}
multiple
/>
</label>
)}
</div>
{images.length === 0 && (
<p className="mt-1 text-sm text-gray-500">
Add up to 9 photos to your profile
{/*<br/>Note: uploads may only work on desktop*/}
</p>
)}
</div>
{error && errorBlock()}
<div>
<button
type="submit"
disabled={isSubmitting || isUploading}
className={`group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white ${
isSubmitting || isUploading
? 'bg-blue-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500`}
>
{isSubmitting || isUploading ? 'Saving...' : 'Save Profile'}
</button>
</div>
{id &&
<div>
<DeleteProfileButton
profileId={id}
onDelete={() => signOut({callbackUrl: "/"})}
/>
</div>
}
</form>
</div>
</div>
);
}

View File

@@ -1,54 +0,0 @@
'use client';
import {DropdownKey} from "@/app/profiles/page";
type DropdownProps = {
id: DropdownKey
options?: string[]
value: string
onChange: (id: DropdownKey, value: string) => void
onFocus?: (id: DropdownKey) => void
onKeyDown?: (id: DropdownKey, key: string) => void
onClick: (id: DropdownKey) => void
}
export default function Dropdown(
{
id,
// options,
value,
onChange,
onFocus,
onKeyDown,
onClick,
}: DropdownProps
) {
return (
<div className="relative">
<div className="flex items-center border border-gray-300 rounded-md shadow-sm">
<input
type="text"
value={value}
onChange={(e) => onChange(id, e.target.value)}
onFocus={() => onFocus?.(id)}
onKeyDown={(e) => onKeyDown?.(id, e.key)}
className="flex-1 min-w-0 block w-full px-3 py-2 rounded-l-md border-0 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Type to search"
/>
<button
type="button"
onClick={(_) => onClick?.(id)}
className="px-3 py-2 border-l border-gray-300 text-gray-500 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
<svg className="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"/>
</svg>
</button>
</div>
</div>
)
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

View File

@@ -1,68 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
/*--background: #0a0a0a;*/
/*--foreground: #ffffff;*/
}
/*@media (prefers-color-scheme: dark) {*/
/* :root {*/
/* --background: #0a0a0a;*/
/* --foreground: #ededed;*/
/* }*/
/*}*/
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
html, body {
background-color: var(--background);
color: var(--foreground);
margin: 0;
padding: 0;
}
/* Style all headings globally */
h1, h2, h3, h4, h5, h6 {
font-family: 'Inter', sans-serif; /* Clean modern font */
font-weight: 600; /* Semi-bold for clarity */
/*color: #111827; !* Near-black text for readability *!*/
line-height: 1.25;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
}
/* Size scaling */
h1 {
font-size: 2rem; /* ~32px */
}
h2 {
font-size: 1.5rem; /* ~24px */
}
h3 {
font-size: 1.25rem; /* ~20px */
}
h4 {
font-size: 1.125rem; /* ~18px */
}
h5 {
font-size: 1rem; /* ~16px */
}
h6 {
font-size: 0.875rem; /* ~14px */
color: #374151; /* Slightly lighter for subheadings */
}
ul {
list-style: disc;
padding-left: 1.25rem;
margin-top: 0.5rem;
}

View File

@@ -1,52 +0,0 @@
// app/layout.tsx
import "./globals.css";
import {ThemeProvider} from 'next-themes';
import {Metadata} from "next";
import Header from "@/app/Header";
import Providers from "@/app/providers";
export const metadata: Metadata = {
title: "IntentionalBond",
description: "A bonding platform for rational thinkers",
};
export default function RootLayout(
{
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning className="dark" >
<body className="dark:bg-gray-900 dark:text-white">
<Providers>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<div className="min-h-screen flex flex-col ">
<Header/>
{children}
{/* Footer */}
<footer className="p-6 text-center text-gray-500">
<div className="mb-2">
<a
href="https://github.com/BayesBond/BayesBond"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-gray-500 hover:text-gray-700 transition"
>
<svg className="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.699 1.028 1.595 1.028 2.688 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
</svg>
View on GitHub
</a>
</div>
<div>© {new Date().getFullYear()} IntentionalBond. All rights reserved.</div>
</footer>
</div>
</ThemeProvider>
</Providers>
</body>
</html>
);
}

View File

@@ -1,53 +0,0 @@
'use client';
import Link from "next/link";
import {aColor} from "@/lib/client/constants";
export default function LearnMorePage() {
return (
<div className="text-gray-600 dark:text-white min-h-screen p-6">
{aColor}
<div className="max-w-3xl mx-auto">
<h1 className="text-3xl font-bold mb-4 text-center">About IntentionalBond</h1>
<div className="et_pb_text_inner">
{/*<h1 id="abstract">Abstract</h1>*/}
<p>Forming and maintaining close connections is fundamental for most peoples mental healthand hence overall
well-being. However, currently available meeting platforms, lacking transparency and searchability, are
deeply failing to bring together thoughtful people. This platform is designed to
foster close friendships and relationships for people who prioritize learning, curiosity, and critical
thinking. The directory of users is fully transparent and each profile contains extensive
information, allowing searches over all users through powerful filtering and sorting methods. To prevent any
value drift from this pro-social mission, the platform will always be free, ad-free, not for profit,
donation-supported, open source, and democratically governed.</p>
<div className="mt-8 flex space-x-4 justify-center">
<Link
href="/manifesto"
className="px-6 py-3 bg-gray-200 dark:bg-gray-600 text-gray-800 dark:text-white text-lg rounded-lg hover:bg-gray-300 dark:hover:bg-gray-500 transition"
>
Manifesto
</Link>
</div>
<h3 id="how-to-help">How to Help</h3>
<h5 id="give-suggestions-or-contribute">Give Suggestions or Contribute</h5>
<p>Give suggestions or show your inclination to contribute through this <a
href="https://forms.gle/tKnXUMAbEreMK6FC6">form</a>!</p>
<h5 id="join-chats">Join Chats</h5>
<p>You can join the community on <a
href="https://join.slack.com/t/bayesmeet/shared_invite/zt-3a2k2ybf4-~S8i0hCuUETojxqNHJbB_g">Slack</a> or <a
href="https://discord.gg/8Vd7jzqjun">Discord</a> to shape and test the productor just to chat with
like-minded people.</p>
<h5 id="share">Share</h5>
<p>Share the idea and article with people who identify with the community values and may benefit from the
product.</p>
<h5 id="donate">Donate</h5>
<p>You can already donate to support the initial infrastructure via <a
href="https://www.paypal.com/paypalme/MartinBraquet">PayPal</a> or <a
href="https://github.com/sponsors/MartinBraquet">GitHub</a> (GitHub has increased transparency, but requires
an account).</p>
<h5 id="github-repo">Source Code</h5>
<p>The source code and instructions for development are available on <a href="https://github.com/BayesBond/BayesBond">GitHub</a>.</p>
</div>
</div>
</div>
);
}

View File

@@ -1,148 +0,0 @@
"use client";
import {signIn} from "next-auth/react";
import {useRouter, useSearchParams} from "next/navigation";
import {Suspense, useEffect, useState} from "react";
import Link from "next/link";
import {FcGoogle} from "react-icons/fc";
export default function LoginPage() {
return (
<Suspense fallback={<div></div>}>
<RegisterComponent/>
</Suspense>
);
}
function RegisterComponent() {
const router = useRouter();
const searchParams = useSearchParams();
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const error = searchParams.get('error');
if (error === 'OAuthAccountNotLinked') {
setError('This email is already registered with a different provider');
} else if (error) {
setError('An error occurred during login');
}
}, [searchParams]);
const handleGoogleSignIn = async () => {
try {
setIsLoading(true);
await signIn('google', {callbackUrl: '/'});
} catch {
setError('Failed to sign in with Google');
setIsLoading(false);
}
};
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
try {
event.preventDefault();
setIsLoading(true);
setError(null);
const formData = new FormData(event.currentTarget);
const response = await signIn("credentials", {
...Object.fromEntries(formData),
redirect: false,
});
if (response?.error) {
setError("Invalid email or password");
setIsLoading(false);
return;
}
router.push("/");
router.refresh();
} catch {
setError("An error occurred during login");
setIsLoading(false);
}
}
console.log('Form rendering');
return (
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold ">
Sign in to your account
</h2>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="email" className="sr-only">
Email address
</label>
<input
id="email"
name="email"
type="email"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Email address"
/>
</div>
<div>
<label htmlFor="password" className="sr-only">
Password
</label>
<input
id="password"
name="password"
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Password"
/>
</div>
</div>
{error && (
<div className="text-red-500 text-sm text-center">{error}</div>
)}
<div className="space-y-4">
<button
type="submit"
disabled={isLoading}
className={`group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${isLoading ? 'opacity-70 cursor-not-allowed' : ''}`}
>
{isLoading ? 'Signing in...' : 'Sign in with Email'}
</button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-gray-50 dark:bg-gray-900 text-gray-500">Or continue with</span>
</div>
</div>
<button
type="button"
onClick={handleGoogleSignIn}
disabled={isLoading}
className="w-full flex items-center justify-center gap-2 py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover: focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-70 disabled:cursor-not-allowed"
>
<FcGoogle className="w-5 h-5"/>
Sign in with Google
</button>
</div>
</form>
<div className="text-center">
<Link href="/register" className="text-blue-600 hover:underline">
No account? Register.
</Link>
</div>
</div>
</div>
);
}

View File

@@ -1,215 +0,0 @@
'use client';
import {aColor, supportEmail} from "@/lib/client/constants";
export default function PrivacyPage() {
return (
<main className="max-w-4xl mx-auto p-8">
{aColor}
<h1 id="abstract">Abstract</h1>
<p>Forming and maintaining close connections is fundamental for most peoples mental healthand hence overall well-being. However, currently available meeting platforms, lacking transparency and searchability, are deeply failing to bring together thoughtful people. This article lays the path for a platform designed to foster close friendships and relationships for people who prioritize learning, curiosity, and critical thinking. The directory of users will be fully transparent and each profile will contain extensive information, allowing searches over all users through powerful filtering and sorting methods. To prevent any value drift from this pro-social mission, the platform will always be free, ad-free, not for profit, donation-supported, open source, and democratically governed. The goal of this article is to better understand the community needs, as well as to gather feedback and collaboration for the suggested implementation.</p>
<h1 id="introduction">Introduction</h1>
<p>Ill explain below my rationale for suggesting the implementation of a bonding platform; since it would be run by volunteers, it goes without saying that those reasons are pro-social.</p>
<p>The starting point is purely personal; Im currently looking for a few more close connections and, like many, struggling to find like-minded peopledespite trying different approaches like dating apps, forums, and real-life communities. Of course, Ive made wonderful connections along the way, but they require a lot of effort to find them out of hundreds of other ones, and they may not lead to the emotional and intellectual closeness that is so fulfilling in close relationships.</p>
<p>Most of us know that relationships have a major positive impact on overall well-being [1], and most of the happiest people have great close relationships [2]. Additionally, although groups are useful for meeting people, the main value of close connections arises from <a href="https://www.lesswrong.com/posts/pfibDHFZ3waBo6pAc/intentionally-making-close-friends">1-to-1 conversations</a>, as they foster <a href="https://www.lesswrong.com/posts/L2GR6TsB9QDqMhWs7/the-value-proposition-of-romantic-relationships">emotional closeness and vulnerability</a>. With that in mind, I spelled out the type of connections that would bring a lot of value to my life: a close friendship or relationship, with an emotionally stable person around my age, who feels connected to rationality, intellectualism, minimalism, nature, and animal welfare. Of course, this description is much too specific to fit anyonelet alone some of the readers. My values may also slightly vary as my views change; but the description paves the way for attracting and finding people who are much more likely to connect with me.</p>
<p>The more values you require in others, the less likely they are to fit them. So, a good approach is to enumerate as many values as possible as long as the whole set still fits a few people out there. I personally love evidence-based learning; this framework has had the most transformative impact on my life so far. Understanding the world through the lenses of rational thinking has deeply stabilizing me, helping me reach some form of internal peaceor robustness to life eventsmuch better than any other practice. It never fails to keep me excited everyday. My goal isnt to change anyones core values, but rather to make the case that there are many people in the world who find a lot of meaning and happiness in rational intellectualism.</p>
<p>With that insight, I narrowed down my most important values to rationality and intellectualismwhich should still apply to 1-2% of the population. Those are the primary values Id like to see in the people close to me and, as a consequence, in the platform that Ill detail below. I imagine a community where people prioritize truth over comfort, depth over instant gratification, and humility over (over)confidence. Such is the essence of epistemological connectionsdeep bonds rooted in mutual curiosity, love of learning, and evidence-based thinking.</p>
<h1 id="core-values">Core Values</h1>
<p>By rationality and intellectualism, I certainly don't allude to cold, elitist traits devoid of any emotional awareness. I don't mean that one should have already polished those traits either; one should simply value them enough to have the potential to approach them in the future.</p>
<p>All along this article, the community should be understood as the set of people who identify with the core values belowwhether or not there exists a platform to connect them. Let's carefully explore and define the 4 core values: rationality, intellectualism, relational fulfillment, and interpersonal maturity.</p>
<h3 id="rationality">Rationality</h3>
<p>Rationality should be understood—very generally—as the systematic process of forming accurate, evidence-based beliefs to achieve some stated goals.</p>
<p>Instead of retreating into relativism—everyones opinion is equally valid—when disagreements arise, one engages in critical thinking by questioning each other down to the core of their beliefs. This truth-seeking process is a collaboration of the minds where everyone wins, and people find it emotionally grounding—not threatening.</p>
<p>Curiosity and depth should rise above comfort; truth requires nuance and thoughtful engagement. Evidence is worth more than status or authority; upon presentation of stronger evidence, one has the intellectual humility to change ones beliefs without feelings of personal attacks. Awareness of ones cognitive biases, intending to reduce them, and saying “I dont know” are fundamental strengths. Likewire, one applauds others when they acknowledge their mistakes, limitations, or change their mind.</p>
<p>Rationality may be seen as the exercise of reason at the expense of the impulses—but certainly not of the emotions as a whole.</p>
<p>Of course, those are very high epistemic standards which, perhaps, no one wholly holds; but, again, intention and care for them are more than enough.</p>
<h3 id="intellectualism">Intellectualism</h3>
<p>There is some overlap with rationality, but intellectualism should principally be understood as deriving profound satisfaction from learning, thinking and sharing ideas.</p>
<p>The intellectual enjoys gaining knowledge for its own sake—which may or may not prove useful in the far future through higher-order effects—or to fulfil more direct goals. Endowed this epistemic drive, they engage in debate, discussions, and longform content (e.g., books, podcasts and forums) covering diverse topics that require the exchange of ideas: philosophy, science, history, art, etc.</p>
<p>Of course, even “truth-seekers” want to be silly and playful from time to time; one just needs to be able to engage in rational intellectualism for important decisions, not all day.</p>
<h3 id="relational-fulfillment-agency">Relational Fulfillment &amp; Agency</h3>
<p>Relational fulfillment should be understood as experiencing satisfaction and completeness within close relationships—agency being the drive to form and maintain them.</p>
<p>Close relationships are relationships endowed with an emotional bond resulting in a profound care (and sometimes love) for each other. Key mechanisms that usually develop close relationships include value alignment, mutual vulnerabilities, proper communication, and the mere exposure effect.</p>
<h3 id="interpersonal-maturity">Interpersonal Maturity</h3>
<p>Interpersonal maturity is an umbrella term that applies to relationships in general. It involves, among others, self-awareness, accountability, transparency, conflict navigation, and the ability to care simply and sanely.</p>
<p>To maintain close connections among rational intellectuals, interpersonal maturity is as fundamental as the three other core values; it is the glue that bonds them together and allows smooth interactions. Indeed, rationality primarily helps determine how to achieve goals, but not which ultimate goals to pursue. So, to understand others and care for them, logic must be paired with self-awareness, emotional intelligence, and an understanding of their deeper influences.</p>
<h3 id="audience">Audience</h3>
<p>Now that the core values are defined, Ill describe some other traits in people that partially correlate with them. (Note that the community is defined based on the core values only; anyone who fits the core value, regardless of the traits below, is already part of the community.)</p>
<p>So, here are some tendencies that highlight how most potential community members might be.</p>
<ul className="list-disc pl-5 space-y-1">
<li>Asking clarifying questions, citing evidence, responding thoughtfully, inviting dialogue before delivering facts</li>
<li>Valuing evidence-based practices (e.g., medicine, economics)</li>
<li>Attending reading groups, philosophy salons, or academic communities</li>
<li>Lifelong learners (e.g., college educated who continues learning on the side)</li>
<li>Enjoying online connections (at first)</li>
<li>Fostering friendships before romance</li>
</ul>
<p>Naturally, the people identifying with the core values are quite rare (probably around 1% of the population). Most people have everyday priorities which clashes with the cognitive load required for rational thinking. Also, seeking the truth is usually linked with an initial change of worldview, which is often destabilizing at first, before becoming stabilizing. Social barriers are there as well, especially under social coherentism or revealed foundationalism—the most popular epistemological frameworks.</p>
<h3 id="revisions">Revisions</h3>
<p>The core values are subject to slight revisions upon receiving feedback from people who mostly align with them. For example, I have no issue making the community more inclusive by adding debate partners (i.e., with no emotional bonding), as long as everyone is transparent about their intentions.</p>
<h1 id="market-review">Market Review</h1>
<p>Ill briefly go over some currently available products / methods that connect people and I'll review if they follow the core values.</p>
<p>Ill start with the traditional dating apps (Tinder, Bumble, and the like), as they are the easiest to reject. Almost every aspect in their business model and functionalities goes against the social mission of bringing together rational intellectuals for close connections. The user base is composed of less than 2% of such people and they cannot be searched with the few available filters. Textual profile information fails to bring any valuable insight into their personality and values, which are the most important causes of good connections. Their business model is even less aligned with the pro-social mission than their functionalities. They aim at maximizing profit by maximizing user retention through manipulative neurological and psychological techniques such as instant dopamine gratification and fear of missing out.</p>
<p>A dating app which could be praised for its more transparent user base is Feeld. There is no swipe; you can browse a directory and like whoever. Their focus on non-traditional relationships and poor filters make it clearly unsuitable for our stated needs, however.</p>
<p>The most popular rationalist communities are LessWrong and its derivatives: Effective Altruism (EA), This Part Of Twitter / Post-Rationalists, Astral Codex Ten, etc.. They are mostly online communities (custom forums, Reddit, Discord, etc.), but most of them organize local meetups. The derivatives mostly embrace the two first core values as well (rationality and intellectualism), and usually add a third one unique to their community. To this day, those communities and their platforms are probably the most likely way to create close relationships between people who follow the core values above. I would love to get some feedback about the number of close connections that emerged from those communities and the proportion of members looking for such connections.</p>
<p>It is yet unclear, however, how much they embrace the third core value (actively seeking to form deep, lasting bonds). Those online platforms do permit to write a profile with interests / preferences and contact info, and to search and contact any other member; this is great for general connections. But they don't allow for detailed, standardized profile information or filters in order to find the people most likely to form deep bonds. So, as of now, the best shot for someone looking for close connections is either to travel to local meetups or to find members with shared values / interests (maybe something they posted or on their profile, with some amount of serendipity) and hope that they are also open to closer bonds. In my opinion, what could be improved on those platforms—or created in a new one—is having access to detailed information about plenty of people and being able to quickly filter the most suitable connections.</p>
<p>The EA community is probably the one that made the most effort to circumvent those limitations and form close, fulfilling relationships. Besides a few unsuccessful calls for building an <a href="https://forum.effectivealtruism.org/posts/atwsMvS8HXaW4QX2h/what-would-you-desire-in-an-ea-dating-site">EA dating app</a>, they published an interesting <a href="https://docs.google.com/spreadsheets/d/1oL25vc5Feg94flPPCO03ujijRvyHffxt0qnI7rU8rdg/edit?gid=0#gid=0">dating spreadsheet</a> that provides <a href="https://www.lesswrong.com/posts/6yiayg5QWtWme4JN8/anatomy-of-a-dating-document">dating documents</a>—which contain information about who they are, what they are looking for, and how to be contacted. The concept of dating docs is a promising tool to connect people with highly specific preferences and standards—as is the case for most rational intellectuals. But, at this stage, those non-standardized documents are scattered across the web, so there is no easy way to filter them.</p>
<p>Other EA platforms for connections include <a href="https://buck-reciprocity.herokuapp.com/">Reprocity</a> and the EA community on Facebook Dating. <a href="https://dateme.directory">DateMe</a>, perhaps appealing to a broader rationalist audience, is a web app with basic filters (gender, age and location) where users can share a dating doc and contact information. These three platforms, however, suffer (again) from poor filtering methods and scarce profile information, making it immensely hard to find close matches.</p>
<p>More recently, a few apps have tried to run against the industrial business model of traditional apps. <a href="https://notazombie.net/landing">Not a Zombie</a> (yes, dont ask me why) is an app that is currently being developed, with a “focus on compatibility first, photos second”. Users would first see a text-based description of other people, and photos would only be revealed after some engagement. Interestingly, they allow any user to message any other user, as they “believe that the act of sending a well-crafted first message can prompt interest even if there wasnt already interest otherwise”. To prevent spamming, however, they would limit the number of profiles visible per day. Many of the app components are valuable food for thoughts, but the app itself—if it ever gets built—would fall short to embrace the core values of the preceding section. It is for dating only, not open-source, and every user may only see a few profiles per day.</p>
<p>Another recent app, <a href="https://datefirefly.com/">Firefly</a>, has reached more popularity while keeping its pro-social mission. Its user base is fully transparent and many interesting prompts bring thoughtful discussions. But, again, profile information and filters are very scarce, so its hard to search the user base and spot like-minded people. The code is not open source either.</p>
<p>Lastly, <a href="https://www.bewelcome.org/">BeWelcome</a>, a French web app focused on hosting and connections, is worth considering for its extreme pro-social mission and worldwide success. It is proud to be completely free, solely funded by donations, ad-free, non-profit, open-source, and run by volunteers. Its governance is partially democratic; they have three types of position: member, BeVolunteer member or board member. A BeVolunteer member is approved by the board members, and the board members get elected by the BeVolunteer members. All decisions are voted by BeVolunteer members only; so the users have no voting right. Profile information is diverse, although tailored for travelers—such as hobbies, languages, and hosting capabilities. Their member directory is transparent, but only searchable by location (and username…).</p>
<p>To conclude, it becomes clear that none of the platforms above fulfill the core values and goals stated in the precedent sections. Please let me know if there is any other platform you may know that better approaches the requires features. Otherwise, I will be happy to build one, as detailed below.</p>
<h1 id="platform-values">Platform Values</h1>
<h3 id="mission">Mission</h3>
<p>The platforms mission is to maximize the number of close, meaningful connections among the community (i.e., people who align with the core values).</p>
<p>To best perform the mission, we need a free, ad-free, transparent, community-driven platform. I describe each aspect in detail below.</p>
<h3 id="free">Free</h3>
<p>To ensure no manipulation for profit, users must have full, free access to the entire platform.</p>
<p>A free platform means freedom from data exploitation and other commercial techniques.</p>
<h3 id="ad-free">Ad-Free</h3>
<p>To avoid dubious external influences as well as a mission drift toward user retention optimization, the platform must be free of any advertisement.</p>
<p>If some mission-aligned institutions sponsor the platform, their acknowledgment may only be shown in textual form outside the main page (e.g., on a “Sponsors” page).</p>
<h3 id="transparent">Transparent</h3>
<p>To foster decentralized trust in the platform and other users, the platform must promote radical transparency. The entire source code must be public on GitHub. In addition to having their implementation open source, all matching, filtering and sorting algorithms must be documented in English on the platform. To helps curious thinkers find each other without endless swiping, all profiles must be searchable.</p>
<p>Data privacy must be promoted as much as code transparency. Users must be made aware of the visibility of their content. Direct messages, login information and other content marked as private must be kept private to the user. Whether some user data are public (for the world wide web) or semi-public (for logged-in users only) must be clearly spelled out.</p>
<h3 id="mission-aligned">Mission-Aligned</h3>
<p>To align with the core values, the platform must promote slowness, reflection, vulnerability, truth-seeking and openness. Tools must be designed for signal, not addiction.</p>
<h3 id="community-driven-governance">Community-Driven Governance</h3>
<p>To prevent the platform from drifting toward exploitative practices, it must be created and maintained by the community, for the community. Decisions must follow a democratic process. Everyone may suggest and implement features.</p>
<h1 id="implementation">Implementation</h1>
<p>Ill describe in more technical details how to implement the platform in order to promote close connections in a very efficient fashion, while staying aligned with the platform values—and hence the core values. It will be very heavy on data and algorithms. The back end will be extensive and accessible to the user, while the design will be clean but minimalist (a drastic paradigm shift from traditional meeting apps).</p>
<h3 id="profile-information">Profile Information</h3>
<p>The profile page should front-load intellectual and psychological alignment, while still being human and approachable. It should contain as much information as possible—this requires a lot of self-awareness. Naturally, many people would be uncomfortable sharing so many personal things about themselves, especially since everyone in the app could view the profile. One easy way to circumvent this concern is to set different levels of privacy for different parts of the profile; the main screen would be open to everyone, and the other parts would be revealed whenever the profile owner feels like the connection has matured enough.</p>
<p>The first screen could thus include:</p>
<ul className="list-disc pl-5 space-y-1">
<li>Name and small headshot (20% of the screen max)</li>
<li>Intellectual topics currently being explored</li>
<li>Favorite intellectual topics</li>
<li>Least favorite intellectual topics</li>
<li>Thinking style</li>
<li>Results from evidence-based personality tests (e.g., Big 5)</li>
<li>Conflict style</li>
<li>Desires: type of connection, activities to do, etc.</li>
</ul>
<p>Many traditional features remain important, such as the level of education, job or studies, hobbies, pets, habits, subcultures, diet, emotional sensitivity, sense of humor, ambition, organization, pet peeves, non-negotiables.</p>
<p>They could mention their physical and mental health—especially some traits that rub people the wrong way, insecurities, biases, triggers, therapy, or the things they are trying to improve about themselves.</p>
<p>Their values must also be clearly stated, as value alignment is a strong indicator of good connections. Moral aspects include community engagement, social justice, and other cause areas.</p>
<p>For people interested in romantic relationships, they would benefit from talking about their love languages (giving and receiving), timeline, romantic orientation, family projects, work-life balance, financial goals / habits, career goals, housing situation (renting vs owning), location, and whether they would date someone who already has kids.</p>
<p>I think it would also be valuable to add a profile variable that tracks when a user was last connected, in order to let any user filter out inactive profiles.</p>
<p>What they are looking for is also paramount for a good match. The user could go through all the features above one more time and write what they would like in their ideal person.</p>
<p>The points above are important to define tags (necessary for filtering), but the user could in addition complete their profile with as much well-formatted text as they want—including a few pictures, link to a blog, contact information, or even some reviews from close people.</p>
<p>To avoid empty profiles, we would set a minimum number of characters to write on their profile. Also, well make some features mandatory (personality traits, intellectual interests, location, desired connection, etc.). An interesting nudge to incite people to fill in their profile would be to add a profile variable for the level of profile completion (in percent); like any other feature, one would be able to filter or sort by profile completion in order to interact with the most completed profiles.</p>
<h5 id="modules">Modules</h5>
<p>Ultimately, we could add modules to raise self-awareness about disagreement styles, thinking styles, intellectual interests, philosophies, etc. I suspect this would add very useful profile information.</p>
<h5 id="prompt-based-messaging">Prompt-based Messaging</h5>
<p>Deep open-ended questions are a great way to deepen connections; they can be answered individually, used as openings, or as tags for filtering. Here are some that I find particularly appealing in light of the core values, but they should probably span many more topics (most of them are cherry-picked from ChatGPT).</p>
<ul className="list-disc pl-5 space-y-1">
<li>How has understanding X helped you suffer less when Y happened?</li>
<li>How have some ideas changed the way you respond to stress?</li>
<li>Whats something you recently changed your mind about after seeing stronger evidence or reasoning?</li>
<li>What do you think counts as a good reason to believe something?</li>
<li>Whats a topic you find endlessly fascinating but rarely get to talk about?</li>
<li>Have you found that understanding Bayesian thinking changed how you handle personal conflict or relationships?</li>
<li>When someone disagrees with you on a topic that matters, how do you approach the conversation?</li>
<li>Has understanding the truth about something ever brought you peace, even if it was difficult at first?</li>
<li>Is there a belief you hold that you wish were false—but still think is probably true?</li>
<li>If we were to spend an afternoon talking, what topic would you bring up first—and why?</li>
<li>When you're in conflict with someone close to you, what happens in your body? What do you notice firstemotions, thoughts, or physical sensations?</li>
<li>Think of a time when you and someone important to you disagreed, but came out stronger after. What do you think made that possible?</li>
</ul>
<p>I also like the idea of user-generated prompts voted by the community.</p>
<h3 id="searching">Searching</h3>
<p>The principal method to find like-minded users will be through advance filters and sorting algorithms across most of the variables defined in each profile.</p>
<p>The difference between filtering and sorting may be subtle but very useful. Filtering simply means finding a subset of the user base that matches some hard criteria. For instance, one could filter for openness (in the Big 5) higher than 50% AND interested in a relationship, OR favorite intellectual topic is neuroscience. With pure filtering, the resulting profiles are presented in no specific order.</p>
<p>Sorting means assigning a number to each profile and ordering them according to that number. The formula which maps each profile onto a number will be fully defined by the user. For instance, it can be "agreeableness in percent + 10 for each currently explored topic belonging to biology”.</p>
<p>So, filtering is for hard desires and sorting is for soft desires. Mixing the two methods provides immense capabilities to find the connections that matter to us the most.</p>
<p>Alternatively, some open users with less specific preferences could enjoy a secondary tool that balances discovery with serendipity (aka the exploration / exploitation trade-off). If desired, it would be easy to provide profiles that fall slightly outside the mentioned preferences. Or one could use filters with no sorting to randomly sample profiles from the filtered subset. At some point, people could use some algorithms (transparent and curated by the community) to sort their filtered selection; but this would always be an opt-in feature that would never override a sorting method implemented by the user.</p>
<h3 id="matching">Matching</h3>
<p>Once users find great people, one needs to configure if they can start a connection.</p>
<p>In the ideal world where everyone is good and caring, one would let all users start a connection with anyone else by allowing direct messaging. In a world full of spams and harassment, one would only be able to “like” a profile; and the conversation would start only when both people like each other. This process would of course be catalyzed by making the list of who likes you fully transparent.</p>
<p>Although Im eager to receive suggestions in the comments or in the suggestion form, my belief in the goodness of humanity makes me advocate for a hybrid model closer to the ideal one: people can direct message anyone, but this opening would be received more quietly than a message from an already connected user. For instance, those new messages could first arrive in a list separate from the one with connected users, and they would not trigger a notification by default. If early evidence shows that this hybrid model is still subject to immense spams, I—as a good rationalist—will be happy to update my prior.</p>
<p>Of course, people are free to share their contact info on their profile (unlike—you guessed it—traditional apps) if they prefer. Likewise, people exchanging messages on the app would be free to share any information at any stage of the connection—including contact info to connect outside the app.</p>
<p>It always feels painful when someone cuts connections without any context. When a person leaves a chat, the platform could provide general guidelines for ethical endings and invite them to fill in a quick form with the reasons why (for the other user).</p>
<p>Now that I presented how all the good things can happen, from joining to meaningful connections, Ill dive more into the technical details and precautions to make this pro-social tool viable under the hood.</p>
<h3 id="tech">Tech</h3>
<p>First of all, building free and open source software (FOSS) is a drastic paradigm shift from current meeting apps, which brings its pros and cons. One major positive side effect of FOSS is the attraction of technically skilled contributors.</p>
<p>The minimum viable product will contain these features:</p>
<ul className="list-disc pl-5 space-y-1">
<li>Authentication</li>
<li>Page listing all the profiles</li>
<li>Search through all the profile variables (intellectual interests, location, cause areas, personality type, conflict style, desired type of connection, prompt answers, gender, etc.)</li>
<li>Direct messaging</li>
<li>(Prompts or Modules for self-awareness)</li>
</ul>
<p>I propose to develop the platform as a web app first, as its much faster (to develop) and works on all devices. For that purpose, using a framework like React for the front-end and a service like Google Firebase for the back-end seems appropriate. I would consider wrapping it around a progressive web app at some point to make it easier to transfer to a mobile app later. Hosting could be under Firebase Hosting, Vercel, Netlify, or even AWS (seems cost-effective at scale, and I know how to use a few things); lets discuss about the best one. I personally have reasonable full-stack skills and dont mind learning on the field, but it would be awesome to get contributions or feedback from developers with specific experience in any of the above frameworks / services.</p>
<p>The software will be under a permissive license; deciding which one is still open.</p>
<p>For optimal user privacy, end-to-end encrypted messaging may be implemented at some point.</p>
<h3 id="branding">Branding</h3>
<p>I suggest the branding to be intellectual: high-contrast design, thought-provoking (but humble) and philosophical tone in a clean / bookish font.</p>
<h3 id="moderation">Moderation</h3>
<p>Strong moderation should be done to filter out spams, harassment, conspiracy theorists and contrarians for the sake of it.</p>
<p>Keeping in mind the advantages of decentralized governance and the limited number of volunteers, I suggest multiple layers of moderation. The first layer will be moderated by the users themselves; they can report and review anyone else—e.g., someone who wrote an inappropriate messages (sexual advances to someone who wrote on their profile that they are not interested, etc.) or a profile with misaligned content (inappropriate images, hate speech, etc.). A profile would be suspended after a few reports.</p>
<p>The second layer will be run by volunteering moderators. Theyll review profiles that got suspended, remove inactive account, and perform other essential tasks.</p>
<p>If a user notices that someones profile isnt accurate, they would be able to softly report it as (anonymous) feedback. This feature would ideally be framed as a helpful “nudge” between two users, as a way to improve profiles and hence successful connections. A profile that keeps receiving soft reports could be scrutinized by moderators.</p>
<p>If too many newcomers are misaligned with the core values, vetting—examining interested users before joining—may be considered at some point. An epistemic and interpersonal onboarding that checks for core principles of rationalism and healthy relationships before signup may help.</p>
<h3 id="governance">Governance</h3>
<p>There are at least two possible models for governance, and I am yet unsure which one would best achieve the platforms end goals.</p>
<p>The first model is cooperative: users or contributors collectively own and manage the platform—they all have voting power and share in decisions.</p>
<p>The second model is a stewardship council: a small group of deeply aligned users would enforce the value by voting on major decisions. This raises questions about how someone would be granted access to that council; elections and rotations would help, I suppose. Regardless, all rationales and votes of that council would be fully transparent / public.</p>
<p>A hybrid model may also work. The co-op would vote on features, and the council would make sure the features are aligned with the core values—e.g., referring to a written constitution.</p>
<p>A constitution would extent the Platform Values section by including, for instance, a mission statement, some core values, a user bill of rights, and governance principles.</p>
<h3 id="finance">Finance</h3>
<p>Finance is a critical part of the platform which may severely (and indirectly) impact the mission. Indeed, this platform will have inescapable expenses, but it doesnt have any clear, easy source of revenue.</p>
<p>On the expense side, since everyone will contribute for free, the main cost related to the app will be hosting servers. Expenses should be minimal, if carefully developed, but I dont have an estimate right now as there are too many uncertainties. Hopefully, well have an estimate per month and per daily user in the next weeks.</p>
<p>On the revenue side, donations (from users, institutions, etc.) will be the source most aligned with our mission. If donations continuously fall short, a donation campaign would be the best band aid. We send emails and add banners to the platform with a very transparent messaging about the platforms financial health and how much it needs to be viable—not unlike those Wikipedia campaigns. As a last resort, optional memberships (e.g., providing voting rights on major decisions or little perks) or one-time lifetime purchase may be considered.</p>
<p>Ideally, we will use <a href="https://opencollective.com">OpenCollective</a> (or similar) as long-term funding platform, as they provide massive transparency; but we cant use them before the community and platform exist, So, we will gather initial funding throughout my personal accounts (see top of the article); please mention “meeting app” if through PayPal. Obviously, I pledge to use the donation solely for the development of the platform and, in the event that the platform fails, to donate the remaining capital to a charity voted by the community. Finally, expenses and revenue will be fully transparent and available in real time on the platform.</p>
<h3 id="extensions">Extensions</h3>
<p>The question of selectivity vs openness is particularly interesting from a utilitarian point of view: how selective should the platform be to maximize overall well-being?</p>
<p>If the platform is very selective, there will be only a couple people with very aligned values. Their well-being will increase a lot, but there are only a few of them. So there is not much overall benefit for society.</p>
<p>If the platform is fully open (everyone can join), there are two possibilities depending on the connection mechanism. If the list of members is opaque and poorly searchable, as in traditional dating apps, it becomes very unlikely to find value-aligned people. Hence, each individuals increase in well-being is negligible, even though there are plenty of people, and there is not much overall benefit for society either.</p>
<p>Qualitatively, the figure below illustrates this trade-off between selectivity and openness for poorly searchable (i.e., Tinder-type) platforms. The quality is the individual increase in well-being, which increases as the platform selects for people strictly following the core values. The quantity is simply the number of users. The platforms overall benefit (read, increase in total well-being) is then the product of quantity and qualityreaching a maximum with a non-extreme selectivity.</p>
<p><img src="https://martinbraquet.com/wp-content/uploads/rational_qualitative.png" alt="" /></p>
<p>The second possibility appears when the members are fully visible and searchable by anyone. In that case, each member can filter and meaningfully engage with the few people aligning with their values. Each individuals increase in well-being becomes important, and there are plenty of people. So, a fully open and searchable platform may bring a lot of overall benefit for society.</p>
<p>Of course, in practice, there will always be interferences within big communities. And, more importantly, a larger platform requires more resources (i.e., funding) and moderation. Thats why the focus of this article is on a specific community for now. Not only do I identify with the rational / intellectual community, but it is also composed of members who are much more likely to contribute (especially on the tech side), making it a very convenient community. But if it creates much greater good, I think it would be worth considering extending the platform at some point in the far futureprovided that it doesnt negatively dilute the community or create brand identity confusion. More than just creating a higher good, a larger user base means economy of scale: donations may scale proportionally while expense per user would diminishmaking the platform more likely to survive financially.</p>
<h3 id="viability">Viability</h3>
<p>Lets think quantitatively about what would be required to make the app useful and viable.</p>
<p>By useful, I mean that we consider it a win (for the mission) when every user makes at least one meaningful connection through the app. Lets make a few assumptions:</p>
<ul className="list-disc pl-5 space-y-1">
<li>Users will only connect with people within their 20-year range, and people are uniformly aged between 20 and 50. So the probability that two users are aged at most 10 years apart is around 50% (20% for the youngest and oldest people, and 67% for most people in the middle).</li>
<li>Users will only connect with people in the same geographical area, and they are uniformly spread across 5 of them. Thats a 20% probability to find someone in the same area.</li>
<li>Users may form a connection with people of any gender (its not a dating-only app, after all).</li>
<li>Since most users would share rational and intellectual interests, their chance of sharing similar interests and personality traits with another user is higher than usual: 5%.</li>
</ul>
<p>So, the probabilities for any two users to form a meaningful connection is 0.5%. That means a minimum viable user base of 200. Once the app reaches this critical mass, I suspect that its value will be tangiblethrough testimonies, etc.</p>
<p>Another critical aspect that requires people is moderation. I would estimate that one moderator for 100-200 users is enoughmost of the users should in principle be less spamming or violent than the average person. That means 2-5 moderators, up to 1000 users.</p>
<p>The number of maintainers can be minimal once the MVP is out; 1 or 2 people who periodically look at issues (especially critical bugs or vulnerabilities) should be enough. If feature requests come out, thats great; but thats a job for kind contributors (not maintainers).</p>
<p>Now, though to a lesser interest, we can estimate the maximum number of users that could use the app. As the app will be in English at first, we narrow down the population to 1.5 billion people. Its a niche topic, so probably only 1% of the population would fit the core values. Lets be gracious and assume that 50% of them are fulfilled and dont more connections. That brings the total addressable market to 7.5 million people.</p>
<h1 id="critiques">Critiques</h1>
<p>Ill list some expected critiques and provide potential solutions.</p>
<h3 id="choice-cognitive-overload">Choice &amp; Cognitive Overload</h3>
<p>When one can see everyone else, its easy to spend more time browsing than engaging. Also, users must actively filter profiles and go through a lot of information, creating much friction. This is a key difference with traditional appsand its clearly a feature, not a bug. I suspect these concerns may only concern to the more casual, less data-savvy users or the ones with less specific preferences. For those people, I would suggest a discovery mode that resembles more traditional apps: a limited number of profiles shown one at a time by a curated (but transparent, of course) algorithm. This casual mode, however, will never reduce the functionalities and efficiency of the explore mode dedicated to power users.</p>
<h3 id="spam-and-harassment">Spam and Harassment</h3>
<p>On a platform where everyone can message each other, its so easy to be spammed and harassed. The Moderation section suggests different strategies. Lets carefully monitor that aspect as the product goes live.</p>
<p>Capping the number of daily messages may be useful as a last resort. I am somewhat reticent to the idea, however, as I believe the app should be fully usable by every good userwhich is the rationale behind making it free. Maybe there is value in capping the number of first / opening messages, instead.</p>
<h3 id="unequal-gender-ratio">Unequal Gender Ratio</h3>
<p>Men are more likely to outnumber women with those niche values, but this should not be an issue as the app wont be focused on dating.</p>
<p>In this situation, women will be at much higher risk of spamming and harassment. If the strategies in the Moderation section fall short, well tackle the issue more aggressively.</p>
<h3 id="self-awareness-dread">Self-awareness Dread</h3>
<p>People usually don't like to write descriptions about themselves. They get descriptions from others and follow self-awareness modules, however. People can also give feedback and suggest modifications.</p>
<h3 id="online-connections">Online Connections</h3>
<p>The platform doesnt target a specific location; until we get thousands of users, its very likely that connected people live far apart. Its a real concern, although the rational / intellectual community has slightly lower needs for in-person connections than the average person, as a lot of their activities pertain to the mind.</p>
<h1 id="premortem">Premortem</h1>
<p>Here Ill identify a few potential problems that may terminate the project and address whether, in my humble opinion, they should be concerning.</p>
<h3 id="what-if-i-don-t-need-the-app-anymore-">What if I dont need the app anymore?</h3>
<p>There will likely a time when I dont intend to use or develop the app anymore. When this happens, the app will likely survive on its own, per the robust mechanisms detailed above (open source, democratic governance, etc.). So, if contributions of all types (donations, moderation, and code maintenance / improvement) are decentralized, my presence, or the lack thereof, should not significantly impact the well functioning of the app.</p>
<h3 id="what-if-donations-can-t-meet-expenses-">What if donations cant meet expenses?</h3>
<p>This is a more critical issue. If donation-based models fail, even for such an app that would require so little financial help, then it simply means that the value of the product is severely below its maintenance cost. And a mission-driven product with net-negative value and no potential for improvement is not fulfilling its mission; so it should be shut down.</p>
<h1 id="roadmap">Roadmap</h1>
<p>If the app attracts enough traction and contributors, a minimum viable product should be available within 1-2 months. If it further gains popularity, features would be released whenever the community implements them.</p>
<h1 id="conclusion">Conclusion</h1>
<p>I hope the reading was valuable. Lets build a better way to form meaningful relationshipsone designed for people who think deeply, value evidence, and want connections that last. If you believe conversations should go deeper than swipes and small talk, join us.<a href="https://martinbraquet.com/meeting-rational/#abstract"></a></p>
</main>
);
}

View File

@@ -1,48 +0,0 @@
'use client';
import ProfilePage from "@/app/profiles/page";
export const dynamic = "force-dynamic"; // This disables SSG and ISR
export default function HomePage() {
const profilePage = () => {
return (
<main className="min-h-screen flex flex-col">
<ProfilePage/>
</main>
)
}
return (
<main className="min-h-screen flex flex-col">
{/* Header */}
{/*<header className="flex justify-between items-center p-2 max-w-6xl mx-auto w-full">*/}
{/* <a */}
{/* href="https://github.com/BayesBond/BayesBond" */}
{/* target="_blank" */}
{/* rel="noopener noreferrer"*/}
{/* className="text-gray-700 hover: transition"*/}
{/* >*/}
{/* <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">*/}
{/* <path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.699 1.028 1.595 1.028 2.688 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />*/}
{/* </svg>*/}
{/* </a>*/}
{/*</header>*/}
{/* Hero Section */}
<section className="flex flex-col items-center justify-center flex-1 text-center px-4">
<h1 className="text-5xl md:text-6xl xs:text-4xl font-extrabold max-w-3xl leading-tight">
IntentionalBond
</h1>
<p className="mt-6 text-lg md:text-xl text-gray-400 max-w-2xl">
Tired of swiping? Just search what you're looking for!
</p>
<div className=" w-full">
{profilePage()}
</div>
</section>
</main>
);
}

View File

@@ -1,53 +0,0 @@
import {supportEmail} from "@/lib/client/constants";
export default function PrivacyPage() {
return (
<main className="max-w-4xl mx-auto p-8">
<h1 className="text-3xl font-semibold text-center mb-6">Privacy Policy</h1>
<p className="text-center mb-12">
Effective Date: January 1, 2025
</p>
<section className="space-y-6 text-base leading-relaxed">
<p>
At <span className="font-semibold">BayesBond</span>, we value transparency
and respect for your data. This Privacy Policy explains how we handle your information.
</p>
<h2 className="text-xl font-semibold">1. Information We Collect</h2>
<p>
We collect basic account details such as your name, email, and profile data.
Additionally, we may collect usage data to improve platform functionality.
</p>
<h2 className="text-xl font-semibold">2. How We Use Your Data</h2>
<p>
Your data is used solely to operate, personalize, and improve the platform.
We do not sell your personal information to third parties.
</p>
<h2 className="text-xl font-semibold">3. Data Storage & Security</h2>
<p>
We use modern encryption and security practices to protect your data.
However, no online system is completely secure, so use the platform responsibly.
</p>
<h2 className="text-xl font-semibold">4. Third-Party Services</h2>
<p>
BayesBond may integrate with third-party tools (e.g., Google Sign-In).
These services have their own privacy policies that we encourage you to review.
</p>
<h2 className="text-xl font-semibold">5. Your Rights</h2>
<p>
You can request deletion of your account and data at any time by contacting {supportEmail}.
</p>
<p className="italic mt-8">
For questions about this Privacy Policy, reach out at {supportEmail}.
</p>
</section>
</main>
);
}

View File

@@ -1,51 +0,0 @@
'use client';
import Link from 'next/link';
import {usePathname, useRouter} from "next/navigation";
import {getProfile} from "@/lib/client/profile";
import {useEffect} from "react";
import {useSession} from "next-auth/react";
export default function ProfilePage() {
const pathname = usePathname(); // Get the current route
const router = useRouter();
const {data: session} = useSession();
useEffect(() => {
async function asyncRun() {
if (!session?.user?.id)
router.push('/login');
}
asyncRun();
}, []);
try {
const header = (
<div className="px-4 py-5 sm:px-6 flex justify-between items-center">
<div>
<h3 className="text-lg leading-6 font-medium ">My Profile</h3>
<p className="mt-1 max-w-2xl text-sm text-gray-500">View and update your profile information</p>
</div>
<Link
href={`/complete-profile?redirect=${encodeURIComponent(pathname)}`}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Edit Profile
</Link>
</div>
)
return (
<div className="min-h-screen py-12 px-4 sm:px-6 lg:px-8">
{getProfile('/api/profile', header)}
</div>
)
;
} catch
(error) {
console.error('Error fetching user data:', error);
return <div className="text-center py-10">Error loading profile. Please try again later.</div>;
}
}

View File

@@ -1,396 +0,0 @@
'use client';
import {useEffect, useRef, useState} from 'react';
import {Gender} from "@prisma/client";
import Dropdown from "@/app/components/dropdown";
import Slider from '@mui/material/Slider';
import {DropdownKey, RangeKey} from "@/app/profiles/page";
import {capitalize} from "@/lib/format";
interface FilterProps {
filters: {
gender: string;
interests: string[];
coreValues: string[];
connections: string[];
causeAreas: string[];
searchQuery: string;
minAge?: number | null;
maxAge?: number | null;
minIntroversion?: number | null;
maxIntroversion?: number | null;
};
onFilterChange: (key: string, value: any) => void;
onShowFilters: (value: boolean) => void;
onToggleFilter: (key: DropdownKey, value: string) => void;
onReset: () => void;
}
export const dropdownConfig: { id: DropdownKey, name: string }[] = [
{id: "connections", name: "Desired Connections"},
{id: "coreValues", name: "Core Values"},
{id: "interests", name: "Core Interests"},
{id: "causeAreas", name: "Cause Areas"},
]
export const rangeConfig: { id: RangeKey, name: string, min: number, max: number }[] = [
{id: "age", name: "Age", min: 15, max: 60},
{id: "introversion", name: "Introversion - Extroversion", min: 0, max: 100},
]
export function ProfileFilters({filters, onFilterChange, onShowFilters, onToggleFilter, onReset}: FilterProps) {
interface Item {
id: DropdownKey;
name: string;
}
const [showFilters, setShowFilters] = useState(true);
const dropDownStates = Object.fromEntries(dropdownConfig.map(({id}) => {
const [all, setAll] = useState<Item[]>([]);
const [selected, setSelected] = useState<Set<string>>(new Set());
const [newValue, setNewValue] = useState('');
const ref = useRef<HTMLDivElement>(null);
const [show, setShow] = useState(false);
return [id, {
options: {value: all, set: setAll},
selected: {value: selected, set: setSelected},
new: {value: newValue, set: setNewValue},
ref: ref,
show: {value: show, set: setShow},
}]
}))
console.log(dropDownStates)
useEffect(() => {
async function fetchOptions() {
try {
const res = await fetch('/api/interests');
if (res.ok) {
const data = await res.json() as Record<string, Item[]>;
console.log(data);
for (const [id, values] of Object.entries(data)) {
console.log(id)
dropDownStates[id].options.set(values);
}
}
} catch (error) {
console.error('Error loading options:', error);
}
}
fetchOptions();
// Close dropdown when clicking outside
const handleClickOutside = (event: MouseEvent) => {
for (const id in dropDownStates) {
const dropdown = dropDownStates[id];
const ref = dropdown.ref;
if (
ref?.current &&
!ref.current.contains(event.target as Node)
) {
dropdown.show?.set?.(false); // Defensive chaining
}
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const toggle = (id: DropdownKey, optionId: string) => {
dropDownStates[id].selected.set(prev => {
const newSet = new Set(prev);
if (newSet.has(optionId)) {
newSet.delete(optionId);
} else {
newSet.add(optionId);
}
return newSet;
});
};
const handleKeyDown = (id: DropdownKey, key: string) => {
if (key === 'Escape') dropDownStates[id].show.set(false);
};
const handleChange = (id: DropdownKey, e: string) => {
dropDownStates[id].new.set(e);
}
const handleFocus = (id: DropdownKey) => {
dropDownStates[id].show.set(true);
}
const handleClick = (id: DropdownKey) => {
const shown = dropDownStates[id].show.value;
dropDownStates[id].show.set(!shown);
}
function getDrowDown(id: DropdownKey, name: string) {
return (
<div key={id + '.div'}>
<div className="relative" ref={dropDownStates[id].ref}>
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-2">
{name}
</label>
<Dropdown
key={id}
id={id}
value={dropDownStates[id].new.value}
onChange={handleChange}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
onClick={handleClick}
/>
{(dropDownStates[id].show.value) && (
<div
className="absolute z-10 mt-1 w-full bg-white dark:bg-gray-900 shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black dark:ring-white ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
{dropDownStates[id].options.value
.filter(v => v.name.toLowerCase().includes(dropDownStates[id].new.value.toLowerCase()))
.map((v) => (
<div
key={v.id}
className=" dark:text-white cursor-default select-none relative py-2 pl-3 pr-9 hover:bg-blue-50 dark:hover:bg-gray-700"
onClick={() => {
onToggleFilter(id, v.name);
toggle(id, v.id);
}}
>
<div className="flex items-center">
<input
type="checkbox"
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
checked={dropDownStates[id].selected.value.has(v.id)}
onChange={() => {
}}
onClick={(e) => e.stopPropagation()}
/>
<span className="font-normal ml-3 block truncate">
{v.name}
</span>
</div>
</div>
))}
</div>
)}
</div>
<div className="flex flex-wrap gap-2 mt-3">
{Array.from(dropDownStates[id].selected.value).map(vId => {
const value = dropDownStates[id].options.value.find(i => i.id === vId);
if (!value) return null;
return (
<span
key={vId}
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 dark:text-white dark:bg-gray-700"
>
{value.name}
<button
type="button"
onClick={(e) => {
e.stopPropagation();
toggle(id, vId);
onToggleFilter(id, value.name);
}}
className="ml-1.5 inline-flex items-center justify-center h-4 w-4 rounded-full bg-blue-200 hover:bg-blue-300 dark:text-white dark:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<span className="sr-only">Remove {value.name}</span>
<svg className="h-2 w-2" fill="currentColor" viewBox="0 0 8 8">
<path
d="M4 3.293L6.646.646a.5.5 0 01.708.708L4.707 4l2.647 2.646a.5.5 0 01-.708.708L4 4.707l-2.646 2.647a.5.5 0 01-.708-.708L3.293 4 .646 1.354a.5.5 0 01.708-.708L4 3.293z"/>
</svg>
</button>
</span>
);
})}
</div>
</div>
)
}
interface Range {
id: RangeKey;
name: string;
min: number;
max: number;
}
const rangeStates = Object.fromEntries(rangeConfig.map(({id}) => {
const [minVal, setMinVal] = useState<number | undefined>(undefined);
const [maxVal, setMaxVal] = useState<number | undefined>(undefined);
return [id, {
minVal,
maxVal,
setMinVal,
setMaxVal,
}];
}))
function getSlider({id, name, min, max}: Range) {
const minStr = 'min' + capitalize(id);
const maxStr = 'max' + capitalize(id);
const {minVal, maxVal, setMinVal, setMaxVal} = rangeStates[id];
return (
<div key={id + '.div'}>
<div className="w-full px-2">
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-2">{name}</label>
<Slider
value={[minVal || min, maxVal || max]}
onChange={(e, value) => {
let [_min, _max] = value;
setMinVal((_min || min) > min ? _min : undefined);
setMaxVal((_max || max) < max ? _max : undefined);
}}
onChangeCommitted={(e, value) => {
let [_min, _max] = value;
onFilterChange(minStr, (_min || min) > min ? _min : undefined);
onFilterChange(maxStr, (_max || max) < max ? _max : undefined);
}}
valueLabelDisplay="auto"
min={min}
max={max}
sx={{
color: '#3B82F6',
'& .MuiSlider-valueLabel': {
backgroundColor: '#3B82F6',
color: '#fff',
},
}}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
{/*<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">Min Age</label>*/}
<input
type="number"
min={min}
max={max}
className="w-full p-2 border rounded-lg"
value={minVal || ''}
onChange={(e) => {
const value = e.target.value ? parseInt(e.target.value) : undefined;
onFilterChange(minStr, value);
setMinVal(value);
}}
placeholder="Min"
/>
</div>
<div>
{/*<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">Max Age</label>*/}
<input
type="number"
min={min}
max={max}
className="w-full p-2 border rounded-lg"
value={maxVal || ''}
onChange={(e) => {
const value = e.target.value ? parseInt(e.target.value) : undefined;
onFilterChange(maxStr, value);
setMaxVal(value);
}}
placeholder="Max"
/>
</div>
</div>
</div>
)
}
return (
<div className="w-full mb-8">
<div className="flex flex-col sm:flex-row gap-4 mb-4">
{/*{showFilters && ()}*/}
<button
onClick={() => {
setShowFilters(!showFilters);
onShowFilters(!showFilters);
}}
className="px-4 py-2 border rounded-lg items-center gap-2 whitespace-nowrap hidden"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"/>
</svg>
{showFilters ? 'Hide' : 'Show'}
</button>
</div>
{showFilters && (
<div className="p-4 rounded-lg shadow-sm border space-y-4">
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">Gender</label>
<select
className="w-full p-2 border rounded-lg"
value={filters.gender}
onChange={(e) => onFilterChange('gender', e.target.value)}
>
<option value="">Any Gender</option>
{Object.keys(Gender).map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
</div>
{getSlider(rangeConfig[0])}
{dropdownConfig.map(({id, name}) => getDrowDown(id, name))}
{getSlider(rangeConfig[1])}
{/*<div>*/}
{/* <label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">Cause Areas</label>*/}
{/* <div className="flex flex-wrap gap-2">*/}
{/* {allCauseAreas.map((cause) => (*/}
{/* <button*/}
{/* key={cause.name}*/}
{/* onClick={() => onToggleFilter('causeAreas', cause.name)}*/}
{/* className={`px-3 py-1 text-sm rounded-full ${*/}
{/* filters.causeAreas.includes(cause.name)*/}
{/* ? 'bg-green-100 dark:text-white dark:bg-green-900 text-green-800 border border-green-200'*/}
{/* : 'bg-gray-100 dark:text-white dark:bg-gray-700 text-gray-800 border border-gray-200 hover:bg-gray-200'*/}
{/* }`}*/}
{/* >*/}
{/* {cause.name}*/}
{/* </button>*/}
{/* ))}*/}
{/* </div>*/}
{/*</div>*/}
</div>
<div className="flex justify-end">
<button
onClick={() => {
onReset();
Object.values(dropDownStates).map((v) => {
v.selected.set(new Set());
});
Object.values(rangeStates).map((v) => {
v.setMaxVal(undefined);
v.setMinVal(undefined);
});
}}
className="px-4 py-2 text-sm text-gray-600 dark:text-white hover:text-gray-800"
>
Reset Filters
</button>
</div>
</div>
)}
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More