63 Commits
1.1.0 ... 1.1.1

Author SHA1 Message Date
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
84 changed files with 1214 additions and 12548 deletions

View File

@@ -4,6 +4,7 @@
# For database connection. A 16-character password with digits and letters.
SUPABASE_DB_PASSWORD=
NEXT_PUBLIC_SUPABASE_KEY=
# For authentication.
# Ask the project admin. Should start with "AIza".

View File

@@ -46,10 +46,12 @@ jobs:
# npx playwright install
- name: Run E2E tests
env:
NEXT_PUBLIC_API_URL: localhost:8088
NEXT_PUBLIC_FIREBASE_ENV: PROD
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_SUPABASE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_KEY }}
run: |
NEXT_PUBLIC_API_URL=localhost:8088 \
NEXT_PUBLIC_FIREBASE_ENV=PROD \
NEXT_PUBLIC_FIREBASE_API_KEY=${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} \
yarn --cwd=web serve &
npx wait-on http://localhost:3000
npx playwright test tests/playwright

View File

@@ -29,7 +29,7 @@ A detailed description of the vision is available [here](https://martinbraquet.c
- [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] Search through most profile variables
- [x] (Set up chat / direct messaging)
- [x] Set up chat / direct messaging
- [x] Set up domain name (https://compassmeet.com)
#### Secondary To Do
@@ -39,8 +39,9 @@ Any action item is open to anyone for collaboration, but the following ones are
- [ ] Add profile features (intellectual interests, cause areas, personality type, conflict style, etc.)
- [ ] Add filters to search through remaining profile features (politics, religion, education level, etc.)
- [ ] Cover with tests (very important, just the test template and framework are ready)
- [ ] Clean up terms and conditions
- [ ] Clean up privacy notice
- [ ] 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)
- [x] Clean up learn more page
- [x] Add dark theme

View File

@@ -11,6 +11,8 @@
set -e
cd "$(dirname "$0")"
source ../../.env
ENV=${1:-prod}
@@ -28,7 +30,6 @@ IMAGE_TAG="${TIMESTAMP}-${GIT_REVISION}"
IMAGE_URL="${REGION}-docker.pkg.dev/${PROJECT}/builds/${SERVICE_NAME}:${IMAGE_TAG}"
echo "🚀 Deploying ${SERVICE_NAME} to ${ENV} ($(date "+%Y-%m-%d %I:%M:%S %p"))"
yarn add tsconfig-paths
yarn build
gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin us-west1-docker.pkg.dev

View File

@@ -1,7 +1,7 @@
{
"name": "@compass/api",
"description": "Backend API endpoints",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"scripts": {
"watch:compile": "npx concurrently \"tsc -b --watch --preserveWatchOutput\" \"(cd ../../common && tsc-alias --watch)\" \"(cd ../shared && tsc-alias --watch)\" \"(cd ../email && tsc-alias --watch)\" \"tsc-alias --watch\"",
@@ -51,8 +51,8 @@
"lodash": "4.17.21",
"pg-promise": "11.4.1",
"posthog-node": "4.11.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-email": "3.0.7",
"resend": "4.1.2",
"string-similarity": "4.0.4",
@@ -63,6 +63,8 @@
},
"devDependencies": {
"@types/cors": "2.8.17",
"@types/ws": "8.5.10"
"@types/ws": "8.5.10",
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0"
}
}

View File

@@ -83,7 +83,7 @@ export const getLovers: APIHandler<'get-lovers'> = async (props, _auth) => {
where(`data->>'userDeleted' != 'true' or data->>'userDeleted' is null`),
name &&
where(`lower(users.name) ilike '%' || lower($(name)) || '%'`, { name }),
where(`lower(users.name) ilike '%' || lower($(name)) || '%' or lower(bio::text) ilike '%' || lower($(name)) || '%'`, { name }),
genders?.length && where(`gender = ANY($(gender))`, { gender: genders }),

View File

@@ -1,36 +1,8 @@
import { APIHandler } from './helpers/endpoint'
import {APIHandler} from './helpers/endpoint'
import {geodbFetch} from "common/geodb";
export const searchLocation: APIHandler<'search-location'> = async (body) => {
const { term, limit } = body
const apiKey = process.env.GEODB_API_KEY
console.log('GEODB_API_KEY', apiKey)
if (!apiKey) {
return { status: 'failure', data: 'Missing GEODB API key' }
}
const host = 'wft-geo-db.p.rapidapi.com'
const baseUrl = `https://${host}/v1/geo`
const url = `${baseUrl}/cities?namePrefix=${term}&limit=${
limit ?? 10
}&offset=0&sort=-population`
try {
const res = await fetch(url, {
method: 'GET',
headers: {
'X-RapidAPI-Key': apiKey,
'X-RapidAPI-Host': host,
},
})
if (!res.ok) {
throw new Error(`HTTP error! Status: ${res.status} ${await res.text()}`)
}
const data = await res.json()
// console.log('GEO DB', data)
return { status: 'success', data: data }
} catch (error: any) {
console.log('failure', error)
return { status: 'failure', data: error.message }
}
const {term, limit} = body
const endpoint = `/cities?namePrefix=${term}&limit=${limit ?? 10}&offset=0&sort=-population`
return await geodbFetch(endpoint)
}

View File

@@ -1,41 +1,17 @@
import { APIHandler } from './helpers/endpoint'
import {APIHandler} from './helpers/endpoint'
import {geodbFetch} from "common/geodb";
const searchNearCityMain = async (cityId: string, radius: number) => {
// Limit to 10 cities for now for free plan, was 100 before (may need to buy plan)
const endpoint = `/cities/${cityId}/nearbyCities?radius=${radius}&offset=0&sort=-population&limit=10`
return await geodbFetch(endpoint)
}
export const searchNearCity: APIHandler<'search-near-city'> = async (body) => {
const { cityId, radius } = body
return await searchNearCityMain(cityId, radius)
}
const searchNearCityMain = async (cityId: string, radius: number) => {
const apiKey = process.env.GEODB_API_KEY
if (!apiKey) {
return { status: 'failure', data: 'Missing GEODB API key' }
}
const host = 'wft-geo-db.p.rapidapi.com'
const baseUrl = `https://${host}/v1/geo`
const url = `${baseUrl}/cities/${cityId}/nearbyCities?radius=${radius}&offset=0&sort=-population&limit=100`
try {
const res = await fetch(url, {
method: 'GET',
headers: {
'X-RapidAPI-Key': apiKey,
'X-RapidAPI-Host': host,
},
})
if (!res.ok) {
throw new Error(`HTTP error! Status: ${res.status}`)
}
const data = await res.json()
return { status: 'success', data: data }
} catch (error) {
return { status: 'failure', data: error }
}
}
export const getNearbyCities = async (cityId: string, radius: number) => {
const result = await searchNearCityMain(cityId, radius)
const cityIds = (result.data.data as any[]).map(

View File

@@ -29,6 +29,7 @@ export const sendNewMatchEmail = async (
react: (
<NewMatchEmail
onUser={lover.user}
email={privateUser.email}
matchedWithUser={matchedWithUser}
matchedLover={lover}
unsubscribeUrl={unsubscribeUrl}
@@ -67,6 +68,7 @@ export const sendNewMessageEmail = async (
toUser={toUser}
channelId={channelId}
unsubscribeUrl={unsubscribeUrl}
email={privateUser.email}
/>
),
})
@@ -82,6 +84,7 @@ export const sendNewMessageEmail = async (
toUser={toUser}
channelId={channelId}
unsubscribeUrl={unsubscribeUrl}
email={privateUser.email}
/>
),
})
@@ -109,6 +112,7 @@ export const sendNewEndorsementEmail = async (
onUser={onUser}
endorsementText={text}
unsubscribeUrl={unsubscribeUrl}
email={privateUser.email}
/>
),
})

View File

@@ -37,7 +37,7 @@ const getResend = () => {
if (resend) return resend
const apiKey = process.env.RESEND_KEY as string
console.log(`RESEND_KEY: ${apiKey}`)
// console.log(`RESEND_KEY: ${apiKey}`)
resend = new Resend(apiKey)
return resend
}

View File

@@ -1,41 +1,31 @@
import {
Body,
Button,
Container,
Column,
Head,
Html,
Img,
Link,
Preview,
Row,
Section,
Text,
} from '@react-email/components'
import { type User } from 'common/user'
import { DOMAIN } from 'common/envs/constants'
import { jamesUser, sinclairUser } from './functions/mock'
import {Body, Button, Column, Container, Head, Html, Preview, Row, Section, Text,} from '@react-email/components'
import {type User} from 'common/user'
import {DOMAIN} from 'common/envs/constants'
import {jamesUser, sinclairUser} from './functions/mock'
import {button, container, content, Footer, main, paragraph} from "email/utils";
interface NewEndorsementEmailProps {
fromUser: User
onUser: User
endorsementText: string
unsubscribeUrl: string
email?: string
}
export const NewEndorsementEmail = ({
fromUser,
onUser,
endorsementText,
unsubscribeUrl,
}: NewEndorsementEmailProps) => {
fromUser,
onUser,
endorsementText,
unsubscribeUrl,
email,
}: NewEndorsementEmailProps) => {
const name = onUser.name.split(' ')[0]
const endorsementUrl = `https://${DOMAIN}/${onUser.username}`
return (
<Html>
<Head />
<Head/>
<Preview>New endorsement from {fromUser.name}</Preview>
<Body style={main}>
<Container style={container}>
@@ -55,15 +45,15 @@ export const NewEndorsementEmail = ({
<Section style={endorsementContainer}>
<Row>
<Column>
<Img
src={fromUser.avatarUrl}
width="50"
height="50"
alt=""
style={avatarImage}
/>
</Column>
{/*<Column>*/}
{/* <Img*/}
{/* src={fromUser.avatarUrl}*/}
{/* width="50"*/}
{/* height="50"*/}
{/* alt=""*/}
{/* style={avatarImage}*/}
{/* />*/}
{/*</Column>*/}
<Column>
<Text style={endorsementTextStyle}>"{endorsementText}"</Text>
</Column>
@@ -75,15 +65,7 @@ export const NewEndorsementEmail = ({
</Section>
</Section>
<Section style={footer}>
<Text style={footerText}>
This e-mail has been sent to {name},{' '}
{/* <Link href={unsubscribeUrl} style={footerLink}>
click here to unsubscribe from this type of notification
</Link>
. */}
</Text>
</Section>
<Footer unsubscribeUrl={unsubscribeUrl} email={email ?? name}/>
</Container>
</Body>
</Html>
@@ -96,37 +78,9 @@ NewEndorsementEmail.PreviewProps = {
endorsementText:
"Sinclair is someone you want to have around because she injects creativity and humor into every conversation, and her laugh is infectious! Not to mention that she's a great employee, treats everyone with respect, and is even-tempered.",
unsubscribeUrl: 'https://compassmeet.com/unsubscribe',
email: 'someone@gmail.com',
} as NewEndorsementEmailProps
const main = {
backgroundColor: '#f4f4f4',
fontFamily: 'Arial, sans-serif',
wordSpacing: 'normal',
}
const container = {
margin: '0 auto',
maxWidth: '600px',
}
const logoContainer = {
padding: '20px 0px 5px 0px',
textAlign: 'center' as const,
backgroundColor: '#ffffff',
}
const content = {
backgroundColor: '#ffffff',
padding: '20px 25px',
}
const paragraph = {
fontSize: '18px',
lineHeight: '24px',
margin: '10px 0',
color: '#000000',
fontFamily: 'Arial, Helvetica, sans-serif',
}
const endorsementContainer = {
margin: '20px 0',
@@ -135,10 +89,6 @@ const endorsementContainer = {
borderRadius: '8px',
}
const avatarImage = {
borderRadius: '50%',
}
const endorsementTextStyle = {
fontSize: '16px',
lineHeight: '22px',
@@ -146,35 +96,4 @@ const endorsementTextStyle = {
color: '#333333',
}
const button = {
backgroundColor: '#4887ec',
borderRadius: '12px',
color: '#ffffff',
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: '16px',
fontWeight: 'semibold',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'inline-block',
padding: '6px 10px',
margin: '10px 0',
}
const footer = {
margin: '20px 0',
textAlign: 'center' as const,
}
const footerText = {
fontSize: '11px',
lineHeight: '22px',
color: '#000000',
fontFamily: 'Ubuntu, Helvetica, Arial, sans-serif',
}
const footerLink = {
color: 'inherit',
textDecoration: 'none',
}
export default NewEndorsementEmail

View File

@@ -1,51 +1,43 @@
import {
Body,
Button,
Container,
Head,
Html,
Img,
Link,
Preview,
Section,
Text,
} from '@react-email/components'
import { DOMAIN } from 'common/envs/constants'
import { type LoverRow } from 'common/love/lover'
import { getLoveOgImageUrl } from 'common/love/og-image'
import { type User } from 'common/user'
import { jamesLover, jamesUser, sinclairUser } from './functions/mock'
import {Body, Button, Container, Head, Html, Preview, Section, Text,} from '@react-email/components'
import {DOMAIN} from 'common/envs/constants'
import {type LoverRow} from 'common/love/lover'
import {type User} from 'common/user'
import {jamesLover, jamesUser, sinclairUser} from './functions/mock'
import {Footer} from "email/utils";
interface NewMatchEmailProps {
onUser: User
matchedWithUser: User
matchedLover: LoverRow
unsubscribeUrl: string
email?: string
}
export const NewMatchEmail = ({
onUser,
matchedWithUser,
matchedLover,
unsubscribeUrl,
}: NewMatchEmailProps) => {
onUser,
matchedWithUser,
matchedLover,
unsubscribeUrl,
email
}: NewMatchEmailProps) => {
const name = onUser.name.split(' ')[0]
const userImgSrc = getLoveOgImageUrl(matchedWithUser, matchedLover)
// const userImgSrc = getLoveOgImageUrl(matchedWithUser, matchedLover)
const userUrl = `https://${DOMAIN}/${matchedWithUser.username}`
return (
<Html>
<Head />
<Head/>
<Preview>You have a new match!</Preview>
<Body style={main}>
<Container style={container}>
{/*<Section style={logoContainer}>*/}
{/* <Img*/}
{/* src="..."*/}
{/* width="550"*/}
{/* height="auto"*/}
{/* alt="compassmeet.com"*/}
{/* />*/}
{/*<Img*/}
{/* src="..."*/}
{/* width="550"*/}
{/* height="auto"*/}
{/* alt="compassmeet.com"*/}
{/*/>*/}
{/*</Section>*/}
<Section style={content}>
@@ -56,31 +48,21 @@ export const NewMatchEmail = ({
</Text>
<Section style={imageContainer}>
<Link href={userUrl}>
<Img
src={userImgSrc}
width="375"
height="200"
alt=""
style={profileImage}
/>
</Link>
{/*<Link href={userUrl}>*/}
{/* <Img*/}
{/* src={userImgSrc}*/}
{/* width="375"*/}
{/* height="200"*/}
{/* alt=""*/}
{/* style={profileImage}*/}
{/* />*/}
{/*</Link>*/}
<Button href={userUrl} style={button}>
View profile
</Button>
</Section>
</Section>
<Section style={footer}>
<Text style={footerText}>
This e-mail has been sent to {name},{' '}
{/* <Link href={unsubscribeUrl} style={footerLink}>
click here to unsubscribe from this type of notification
</Link>
. */}
</Text>
</Section>
<Footer unsubscribeUrl={unsubscribeUrl} email={email ?? name}/>
</Container>
</Body>
</Html>
@@ -91,11 +73,12 @@ NewMatchEmail.PreviewProps = {
onUser: sinclairUser,
matchedWithUser: jamesUser,
matchedLover: jamesLover,
email: 'someone@gmail.com',
unsubscribeUrl: 'https://compassmeet.com/unsubscribe',
} as NewMatchEmailProps
const main = {
backgroundColor: '#f4f4f4',
// backgroundColor: '#f4f4f4',
fontFamily: 'Arial, sans-serif',
wordSpacing: 'normal',
}
@@ -147,21 +130,4 @@ const button = {
margin: '10px 0',
}
const footer = {
margin: '20px 0',
textAlign: 'center' as const,
}
const footerText = {
fontSize: '11px',
lineHeight: '22px',
color: '#000000',
fontFamily: 'Ubuntu, Helvetica, Arial, sans-serif',
}
const footerLink = {
color: 'inherit',
textDecoration: 'none',
}
export default NewMatchEmail

View File

@@ -20,6 +20,7 @@ import {
} from './functions/mock'
import { DOMAIN } from 'common/envs/constants'
import { getLoveOgImageUrl } from 'common/love/og-image'
import {button, container, content, Footer, imageContainer, main, paragraph, profileImage} from "email/utils";
interface NewMessageEmailProps {
fromUser: User
@@ -27,6 +28,7 @@ interface NewMessageEmailProps {
toUser: User
channelId: number
unsubscribeUrl: string
email?: string
}
export const NewMessageEmail = ({
@@ -35,6 +37,7 @@ export const NewMessageEmail = ({
toUser,
channelId,
unsubscribeUrl,
email,
}: NewMessageEmailProps) => {
const name = toUser.name.split(' ')[0]
const creatorName = fromUser.name
@@ -62,15 +65,15 @@ export const NewMessageEmail = ({
<Text style={paragraph}>{creatorName} just messaged you!</Text>
<Section style={imageContainer}>
<Link href={messagesUrl}>
<Img
src={userImgSrc}
width="375"
height="200"
alt={`${creatorName}'s profile`}
style={profileImage}
/>
</Link>
{/*<Link href={messagesUrl}>*/}
{/* <Img*/}
{/* src={userImgSrc}*/}
{/* width="375"*/}
{/* height="200"*/}
{/* alt={`${creatorName}'s profile`}*/}
{/* style={profileImage}*/}
{/* />*/}
{/*</Link>*/}
<Button href={messagesUrl} style={button}>
View message
@@ -78,15 +81,7 @@ export const NewMessageEmail = ({
</Section>
</Section>
<Section style={footer}>
<Text style={footerText}>
This e-mail has been sent to {name},{' '}
{/* <Link href={unsubscribeUrl} style={{ color: 'inherit', textDecoration: 'none' }}>
click here to unsubscribe from this type of notification
</Link>
. */}
</Text>
</Section>
<Footer unsubscribeUrl={unsubscribeUrl} email={email ?? name}/>
</Container>
</Body>
</Html>
@@ -98,72 +93,9 @@ NewMessageEmail.PreviewProps = {
fromUserLover: jamesLover,
toUser: sinclairUser,
channelId: 1,
email: 'someone@gmail.com',
unsubscribeUrl: 'https://compassmeet.com/unsubscribe',
} as NewMessageEmailProps
const main = {
backgroundColor: '#f4f4f4',
fontFamily: 'Arial, sans-serif',
wordSpacing: 'normal',
}
const container = {
margin: '0 auto',
maxWidth: '600px',
}
const logoContainer = {
padding: '20px 0px 5px 0px',
textAlign: 'center' as const,
backgroundColor: '#ffffff',
}
const content = {
backgroundColor: '#ffffff',
padding: '20px 25px',
}
const paragraph = {
fontSize: '18px',
lineHeight: '24px',
margin: '10px 0',
color: '#000000',
fontFamily: 'Arial, Helvetica, sans-serif',
}
const imageContainer = {
textAlign: 'center' as const,
margin: '20px 0',
}
const profileImage = {
// border: '1px solid #ec489a',
}
const button = {
backgroundColor: '#4887ec',
borderRadius: '12px',
color: '#ffffff',
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: '16px',
fontWeight: 'semibold',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'inline-block',
padding: '6px 10px',
margin: '10px 0',
}
const footer = {
margin: '20px 0',
textAlign: 'center' as const,
}
const footerText = {
fontSize: '11px',
lineHeight: '22px',
color: '#000000',
fontFamily: 'Ubuntu, Helvetica, Arial, sans-serif',
}
export default NewMessageEmail

View File

@@ -16,7 +16,7 @@ export const Test = (props: { name: string }) => {
}
Test.PreviewProps = {
name: 'Clarity',
name: 'Friend',
}
export default Test

View File

@@ -0,0 +1,117 @@
import {Link, Row, Section, Text} from "@react-email/components";
interface Props {
email?: string
unsubscribeUrl: string
}
export const Footer = ({
email,
unsubscribeUrl,
}: Props) => {
return <Section style={footer}>
<hr style={{border: 'none', borderTop: '1px solid #e0e0e0', margin: '20px 0'}}/>
<Row>
<Text style={footerText}>
Compass © {new Date().getFullYear()}
</Text>
{/*<Row>*/}
{/* <Link href="https://github.com/CompassMeet/Compass">*/}
{/* <TbBrandGithub size={36} color={'black'}/>*/}
{/* </Link>*/}
{/* <Link href="https://discord.gg/8Vd7jzqjun">*/}
{/* <TbBrandDiscord size={36} color={'black'}/>*/}
{/* </Link>*/}
{/* <Link href="https://patreon.com/CompassMeet">*/}
{/* <TbBrandPatreon size={36} color={'black'}/>*/}
{/* </Link>*/}
{/* <Link href="https://www.paypal.com/paypalme/MartinBraquet">*/}
{/* <TbBrandPaypal size={36} color={'black'}/>*/}
{/* </Link>*/}
{/*</Row>*/}
<Text style={footerText}>
The email was sent to {email}. To no longer receive these emails, unsubscribe {' '}
<Link href={unsubscribeUrl}>
here
</Link>
.
</Text>
</Row>
</Section>
}
export const footer = {
margin: '20px 0',
textAlign: 'center' as const,
}
export const footerText = {
fontSize: '11px',
lineHeight: '22px',
color: '#000000',
fontFamily: 'Ubuntu, Helvetica, Arial, sans-serif',
}
export const blackLinks = {
color: 'black'
}
// const footerLink = {
// color: 'inherit',
// textDecoration: 'none',
// }
export const main = {
// backgroundColor: '#f4f4f4',
fontFamily: 'Arial, sans-serif',
wordSpacing: 'normal',
}
export const container = {
margin: '0 auto',
maxWidth: '600px',
}
export const logoContainer = {
padding: '20px 0px 5px 0px',
textAlign: 'center' as const,
backgroundColor: '#ffffff',
}
export const content = {
backgroundColor: '#ffffff',
padding: '20px 25px',
}
export const paragraph = {
fontSize: '18px',
lineHeight: '24px',
margin: '10px 0',
color: '#000000',
fontFamily: 'Arial, Helvetica, sans-serif',
}
export const imageContainer = {
textAlign: 'center' as const,
margin: '20px 0',
}
export const profileImage = {
// border: '1px solid #ec489a',
}
export const button = {
backgroundColor: '#4887ec',
borderRadius: '12px',
color: '#ffffff',
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: '16px',
fontWeight: 'semibold',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'inline-block',
padding: '6px 10px',
margin: '10px 0',
}

View File

@@ -1,22 +1,23 @@
{
"name": "react-email-starter",
"version": "0.1.9",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "email dev",
"dev": "email dev --port 3001",
"build": "tsc -b"
},
"dependencies": {
"@react-email/components": "0.0.33",
"react": "19.0.0",
"react-dom": "19.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-email": "3.0.7",
"react-icons": "5.5.0",
"resend": "4.1.2"
},
"devDependencies": {
"@types/html-to-text": "9.0.4",
"@types/prismjs": "1.26.5",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4"
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0"
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -65,7 +65,7 @@ const newClient = (
...settings,
}
console.log(config)
// console.log(config)
return pgp(config)
}

View File

@@ -19,4 +19,8 @@ BEGIN;
\i backend/supabase/user_events.sql
\i backend/supabase/user_notifications.sql
\i backend/supabase/functions_others.sql
\i backend/supabase/reports.sql
\i backend/supabase/migrations/20250910_bio_to_jsonb.sql
\i backend/supabase/migrations/20250910_add_bio_text.sql
\i backend/supabase/migrations/20250910_pg_trgm.sql
COMMIT;

View File

@@ -1,23 +0,0 @@
-- This file is copied from https://github.com/manifoldmarkets/manifold/blob/main/backend/supabase/reports.sql
create table if not exists
reports (
content_id text not null,
content_owner_id text not null,
content_type text not null,
created_time timestamp with time zone default now(),
description text,
id text default uuid_generate_v4 () not null,
parent_id text,
parent_type text,
user_id text not null
);
-- Foreign Keys
alter table reports
add constraint reports_content_owner_id_fkey foreign key (content_owner_id) references users (id);
alter table reports
add constraint reports_user_id_fkey foreign key (user_id) references users (id);
-- Row Level Security
alter table reports enable row level security;

View File

@@ -0,0 +1,22 @@
ALTER TABLE lovers ADD COLUMN bio_text tsvector;
CREATE OR REPLACE FUNCTION lovers_bio_tsvector_update()
RETURNS trigger AS $$
BEGIN
new.bio_text := to_tsvector(
'english',
(
SELECT string_agg(trim(both '"' from x::text), ' ')
FROM jsonb_path_query(new.bio, '$.**.text'::jsonpath) AS x
)
);
RETURN new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER lovers_bio_tsvector_trigger
BEFORE INSERT OR UPDATE OF bio ON lovers
FOR EACH ROW EXECUTE FUNCTION lovers_bio_tsvector_update();
create index on lovers using gin(bio_text);

View File

@@ -0,0 +1,5 @@
-- 1. Drop the old column
alter table lovers drop column if exists bio;
-- 2. Add the new column as jsonb
alter table lovers add column bio jsonb;

View File

@@ -0,0 +1,4 @@
create extension if not exists pg_trgm;
CREATE INDEX lovers_bio_trgm_idx
ON lovers USING gin ((bio::text) gin_trgm_ops);

View File

@@ -51,10 +51,9 @@ export const baseLoversSchema = z.object({
pref_age_max: z.number().min(18).max(1000),
pref_relation_styles: z.array(
z.union([
z.literal('mono'),
z.literal('poly'),
z.literal('open'),
z.literal('other'),
z.literal('collaboration'),
z.literal('friendship'),
z.literal('relationship'),
])
),
wants_kids_strength: z.number(),

View File

@@ -46,7 +46,7 @@ export const PROD_CONFIG: EnvConfig = {
cloudRunId: 'w3txbmd3ba',
cloudRunRegion: 'uc',
supabaseInstanceId: 'ltzepxnhhnrnvovqblfr',
supabaseAnonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imx0emVweG5oaG5ybnZvdnFibGZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTU5NjczNjgsImV4cCI6MjA3MTU0MzM2OH0.pbazcrVOG7Kh_IgblRu2VAfoBe3-xheNfRzAto7xvzY',
supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_KEY || '',
apiEndpoint: 'api.compassmeet.com',
adminIds: [
'0vaZsIJk9zLVOWY4gb61gTrRIU73', // Martin

32
common/src/geodb.ts Normal file
View File

@@ -0,0 +1,32 @@
export const geodbHost = 'wft-geo-db.p.rapidapi.com'
export const geodbFetch = async (endpoint: string) => {
const apiKey = process.env.GEODB_API_KEY
if (!apiKey) {
return {status: 'failure', data: 'Missing GEODB API key'}
}
const baseUrl = `https://${geodbHost}/v1/geo`
const url = `${baseUrl}${endpoint}`
try {
const res = await fetch(url, {
method: 'GET',
headers: {
'X-RapidAPI-Key': apiKey,
'X-RapidAPI-Host': geodbHost,
},
})
if (!res.ok) {
throw new Error(`HTTP error! Status: ${res.status} ${await res.text()}`)
}
const data = await res.json()
console.log('geodbFetch', endpoint, data)
return {status: 'success', data}
} catch (error) {
console.log('geodbFetch', endpoint, error)
return {status: 'failure', data: error}
}
}

View File

@@ -4,7 +4,8 @@ const isPreferredGender = (
preferredGenders: string[] | undefined,
gender: string | undefined
) => {
if (preferredGenders === undefined || gender === undefined) return true
// console.log('isPreferredGender', preferredGenders, gender)
if (preferredGenders === undefined || preferredGenders.length === 0 || gender === undefined) return true
// If simple gender preference, don't include non-binary.
if (
@@ -17,6 +18,7 @@ const isPreferredGender = (
}
export const areGenderCompatible = (lover1: LoverRow, lover2: LoverRow) => {
// console.log('areGenderCompatible', isPreferredGender(lover1.pref_gender, lover2.gender), isPreferredGender(lover2.pref_gender, lover1.gender))
return (
isPreferredGender(lover1.pref_gender, lover2.gender) &&
isPreferredGender(lover2.pref_gender, lover1.gender)

View File

@@ -5,11 +5,12 @@ export const SITE_ORDER = [
'bluesky',
'mastodon',
'substack',
// 'onlyfans',
'paypal',
'instagram',
'github',
'linkedin',
'facebook',
'patreon',
'spotify',
] as const
@@ -29,6 +30,8 @@ const stripper: { [key in Site]: (input: string) => string } = {
.replace(/^@/, '')
.replace(/\/$/, ''),
discord: (s) => s,
paypal: (s) => s,
patreon: (s) => s,
bluesky: (s) =>
s
.replace(/^(https?:\/\/)?(www\.)?bsky\.app\/profile\//, '')
@@ -83,6 +86,8 @@ const urler: { [key in Site]: (handle: string) => string } = {
linkedin: (s) => `https://linkedin.com/in/${s}`,
facebook: (s) => `https://facebook.com/${s}`,
spotify: (s) => `https://open.spotify.com/user/${s}`,
paypal: (s) => `https://paypal.com/user/${s}`,
patreon: (s) => `https://patreon.com/user/${s}`,
}
export const PLATFORM_LABELS: { [key in Site]: string } = {
@@ -98,4 +103,6 @@ export const PLATFORM_LABELS: { [key in Site]: string } = {
linkedin: 'LinkedIn',
facebook: 'Facebook',
spotify: 'Spotify',
paypal: 'Paypal',
patreon: 'Patreon',
}

View File

@@ -30,7 +30,7 @@ export function createClient(
opts?: SupabaseClientOptionsGeneric<'public'>
) {
const url = `https://${instanceId}.supabase.co`
console.log('createClient', instanceId, key, opts)
// console.log('createClient', instanceId, key, opts)
return createClientGeneric(
url,
key,

View File

@@ -68,7 +68,7 @@ export const getNotificationDestinationsForUser = (
destinations.includes('browser') && !opt_out.includes('browser'),
sendToMobile:
destinations.includes('mobile') && !opt_out.includes('mobile'),
unsubscribeUrl: 'TODO',
unsubscribeUrl: 'https://compassmeet.com/notifications',
urlToManageThisNotification: '/notifications',
}
}

View File

File diff suppressed because it is too large Load Diff

10
install.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"
rm -rf node_modules web/node_modules backend/api/node_modules backend/email/node_modules command/node_modules backend/shared/node_modules
yarn install

View File

@@ -1,6 +0,0 @@
psql \
-h db.ltzepxnhhnrnvovqblfr.supabase.co \
-p 5432 \
-d postgres \
-U postgres \
-f migration.sql

View File

@@ -1,6 +1,6 @@
{
"name": "compass",
"version": "1.1.0",
"version": "1.1.1",
"private": true,
"workspaces": [
"common",
@@ -22,22 +22,12 @@
},
"dependencies": {
"@playwright/test": "^1.54.2",
"@tiptap/core": "2.3.2",
"@tiptap/extension-blockquote": "2.3.2",
"@tiptap/extension-bold": "2.3.2",
"@tiptap/extension-bubble-menu": "2.3.2",
"@tiptap/extension-floating-menu": "2.3.2",
"@tiptap/extension-image": "2.3.2",
"@tiptap/extension-link": "2.3.2",
"@tiptap/extension-mention": "2.3.2",
"@tiptap/html": "2.3.2",
"@tiptap/starter-kit": "2.3.2",
"@tiptap/suggestion": "2.3.2",
"colorette": "^2.0.20",
"react-markdown": "*",
"prismjs": "^1.30.0"
},
"devDependencies": {
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
@@ -67,6 +57,13 @@
"@tiptap/extension-mention": "2.3.2",
"@tiptap/html": "2.3.2",
"@tiptap/starter-kit": "2.3.2",
"@tiptap/pm": "2.3.2",
"@tiptap/suggestion": "2.3.2"
},
"resolutions": {
"react": "18.2.0",
"react-dom": "18.2.0",
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0"
}
}

View File

@@ -1,5 +1,9 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"
ENV=${1:-prod}
PROJECT=$2
case $ENV in

15
scripts/migrate.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"/..
source .env
export PGPASSWORD=$SUPABASE_DB_PASSWORD
psql \
-h db.ltzepxnhhnrnvovqblfr.supabase.co \
-p 5432 \
-d postgres \
-U postgres \
-f backend/supabase/migration.sql

View File

@@ -41,6 +41,8 @@ export function CopyLinkOrShareButton(props: {
setTimeout(() => setIsSuccess(false), 2000) // Reset after 2 seconds
}
const content = isSuccess ? 'Copied!' : children
return (
<ToolTipOrDiv
hasChildren={!!children}
@@ -72,7 +74,7 @@ export function CopyLinkOrShareButton(props: {
aria-hidden="true"
/>
)}
{children}
{content}
</Button>
</ToolTipOrDiv>
)

View File

@@ -1,12 +1,15 @@
import clsx from 'clsx'
import { firebaseLogin } from 'web/lib/firebase/users'
import { Button } from './button'
import { Col } from '../layout/col'
import { Row } from 'web/components/layout/row'
import {firebaseLogin} from 'web/lib/firebase/users'
import {Button} from './button'
import {Col} from '../layout/col'
import {Row} from 'web/components/layout/row'
import {ButtonHTMLAttributes} from "react"
import {FcGoogle} from "react-icons/fc"
export const SidebarSignUpButton = (props: { className?: string }) => {
const { className } = props
const {className} = props
return (
<Col className={clsx('mt-4', className)}>
@@ -43,3 +46,29 @@ export const GoogleSignInButton = (props: { onClick: () => any }) => {
</Button>
)
}
type GoogleButtonProps = {
onClick: () => void
isLoading?: boolean
} & ButtonHTMLAttributes<HTMLButtonElement>
export function GoogleButton({onClick, isLoading = false, ...props}: GoogleButtonProps) {
return (
<button
type="button"
onClick={onClick}
disabled={isLoading}
className={clsx(
"w-full flex items-center justify-center gap-2 py-2 px-4 border border-gray-300",
"rounded-full shadow-sm text-sm font-medium",
"hover:bg-canvas-25 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
"disabled:opacity-70 disabled:cursor-not-allowed"
)}
{...props}
>
<FcGoogle className="w-5 h-5"/>
{isLoading ? "Loading..." : "Google"}
</button>
)
}

View File

@@ -0,0 +1,13 @@
export const RELATIONSHIP_CHOICES = {
// Monogamous: 'mono',
// Polyamorous: 'poly',
// 'Open Relationship': 'open',
// Other: 'other',
Collaboration: 'collaboration',
Friendship: 'friendship',
Relationship: 'relationship',
};
export const REVERTED_RELATIONSHIP_CHOICES = Object.fromEntries(
Object.entries(RELATIONSHIP_CHOICES).map(([key, value]) => [value, key])
);

View File

@@ -72,25 +72,25 @@ export function DesktopFilters(props: {
popoverClassName="bg-canvas-50"
/>
{/* PREFERRED GENDER */}
<CustomizeableDropdown
buttonContent={(open: boolean) => (
<DropdownButton
content={
<PrefGenderFilterText
pref_gender={filters.pref_gender as Gender[]}
highlightedClass={open ? 'text-primary-500' : undefined}
/>
}
open={open}
/>
)}
dropdownMenuContent={
<Col>
<PrefGenderFilter filters={filters} updateFilter={updateFilter} />
</Col>
}
popoverClassName="bg-canvas-50"
/>
{/*<CustomizeableDropdown*/}
{/* buttonContent={(open: boolean) => (*/}
{/* <DropdownButton*/}
{/* content={*/}
{/* <PrefGenderFilterText*/}
{/* pref_gender={filters.pref_gender as Gender[]}*/}
{/* highlightedClass={open ? 'text-primary-500' : undefined}*/}
{/* />*/}
{/* }*/}
{/* open={open}*/}
{/* />*/}
{/* )}*/}
{/* dropdownMenuContent={*/}
{/* <Col>*/}
{/* <PrefGenderFilter filters={filters} updateFilter={updateFilter} />*/}
{/* </Col>*/}
{/* }*/}
{/* popoverClassName="bg-canvas-50"*/}
{/*/>*/}
{/* AGE RANGE */}
<CustomizeableDropdown
buttonContent={(open: boolean) => (

View File

@@ -45,9 +45,7 @@ export function GenderFilter(props: {
choices={{
Women: 'female',
Men: 'male',
'Non-binary': 'non-binary',
'Trans women': 'trans-female',
'Trans men': 'trans-male',
'Other': 'other',
}}
onChange={(c) => {
updateFilter({ genders: c })

View File

@@ -86,22 +86,22 @@ export function MobileFilters(props: {
<GenderFilter filters={filters} updateFilter={updateFilter} />
</MobileFilterSection>
{/* PREFERRED GENDER */}
<MobileFilterSection
title="Interested in"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={hasAny(filters.pref_gender)}
selection={
<PrefGenderFilterText
pref_gender={filters.pref_gender as Gender[]}
highlightedClass={
hasAny(filters.pref_gender) ? 'text-primary-600' : 'text-ink-400'
}
/>
}
>
<PrefGenderFilter filters={filters} updateFilter={updateFilter} />
</MobileFilterSection>
{/*<MobileFilterSection*/}
{/* title="Interested in"*/}
{/* openFilter={openFilter}*/}
{/* setOpenFilter={setOpenFilter}*/}
{/* isActive={hasAny(filters.pref_gender)}*/}
{/* selection={*/}
{/* <PrefGenderFilterText*/}
{/* pref_gender={filters.pref_gender as Gender[]}*/}
{/* highlightedClass={*/}
{/* hasAny(filters.pref_gender) ? 'text-primary-600' : 'text-ink-400'*/}
{/* }*/}
{/* />*/}
{/* }*/}
{/*>*/}
{/* <PrefGenderFilter filters={filters} updateFilter={updateFilter} />*/}
{/*</MobileFilterSection>*/}
{/* AGE RANGE */}
<MobileFilterSection
title="Age"

View File

@@ -54,9 +54,7 @@ export function PrefGenderFilter(props: {
choices={{
Women: 'female',
Men: 'male',
'Non-binary': 'non-binary',
'Trans women': 'trans-female',
'Trans men': 'trans-male',
Other: 'other',
}}
onChange={(c) => {
updateFilter({ pref_gender: c })

View File

@@ -1,22 +1,21 @@
import clsx from 'clsx'
import {
RelationshipType,
convertRelationshipType,
} from 'web/lib/util/convert-relationship-type'
import {convertRelationshipType, RelationshipType,} from 'web/lib/util/convert-relationship-type'
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
import { FilterFields } from './search'
import { MultiCheckbox } from 'web/components/multi-checkbox'
import {FilterFields} from './search'
import {MultiCheckbox} from 'web/components/multi-checkbox'
import {RELATIONSHIP_CHOICES} from "web/components/filters/choices";
export function RelationshipFilterText(props: {
relationship: RelationshipType[] | undefined
highlightedClass?: string
}) {
const { relationship, highlightedClass } = props
const {relationship, highlightedClass} = props
const relationshipLength = (relationship ?? []).length
if (!relationship || relationshipLength < 1) {
return (
<span className={clsx('text-semibold', highlightedClass)}>Any style</span>
<span className={clsx('text-semibold', highlightedClass)}>Any connection</span>
)
}
@@ -49,20 +48,13 @@ export function RelationshipFilter(props: {
filters: Partial<FilterFields>
updateFilter: (newState: Partial<FilterFields>) => void
}) {
const { filters, updateFilter } = props
const {filters, updateFilter} = props
return (
<MultiCheckbox
selected={filters.pref_relation_styles ?? []}
choices={
{
Monogamous: 'mono',
Polyamorous: 'poly',
'Open Relationship': 'open',
Other: 'other',
} as any
}
choices={RELATIONSHIP_CHOICES as any}
onChange={(c) => {
updateFilter({ pref_relation_styles: c })
updateFilter({pref_relation_styles: c})
}}
/>
)

View File

@@ -61,7 +61,7 @@ export const Search = (props: {
<Row className={'mb-2 justify-between gap-2'}>
<Input
value={filters.name ?? ''}
placeholder={'Search name'}
placeholder={'Search anything...'}
className={'w-full max-w-xs'}
onChange={(e) => {
updateFilter({ name: e.target.value })

View File

@@ -1,7 +1,6 @@
import {useEffect} from "react";
import {Col} from "web/components/layout/col";
import {Button} from "web/components/buttons/button";
import {signupRedirect} from "web/lib/util/signup";
import {SignUpButton} from "web/components/nav/love-sidebar";
export function AboutBox(props: {
title: string
@@ -48,18 +47,15 @@ export function LoggedOutHome() {
return (
<>
<Col className="mb-4 gap-2 lg:hidden">
<Button
className="flex-1 fixed top-4 left-1/2 transform -translate-x-1/2 w-full"
color="gradient"
<SignUpButton
className="mt-4 flex-1 fixed bottom-[55px] w-full left-0 right-0 z-10 mx-auto px-4"
size="xl"
onClick={signupRedirect}
>
Sign up
</Button>
text="Sign up"
/>
{/*<SignUpAsMatchmaker className="flex-1"/>*/}
</Col>
<h1
className="pt-12 pb-2 text-7xl md:text-8xl xs:text-6xl font-extrabold max-w-4xl leading-tight xl:whitespace-nowrap md:whitespace-nowrap text-center">
className="pt-12 pb-2 text-7xl md:text-8xl xs:text-6xl font-extrabold leading-tight xl:whitespace-nowrap md:whitespace-nowrap text-center">
Don't Swipe.<br/>
<span id="typewriter"></span>
<span id="cursor" className="animate-pulse">|</span>
@@ -67,9 +63,9 @@ export function LoggedOutHome() {
<div className="py-8"></div>
<h3
className="text-2xl font-bold text-center">
A focused platform for real connectionsbuilt with purpose and speed.
Find people who share your values, not just your photos.
</h3>
<div className="w-full bg-gray-50 dark:bg-gray-900 py-8 mt-20">
<div className="w-full py-8 mt-20">
<div className="max-w-6xl mx-auto px-4">
<div className="grid md:grid-cols-3 gap-8 text-center">
<AboutBox title="Radically Transparent" text="No algorithms. Every profile searchable."/>
@@ -78,6 +74,7 @@ export function LoggedOutHome() {
</div>
</div>
</div>
<div className="block lg:hidden h-12"></div>
</>
);
}

View File

@@ -62,7 +62,7 @@ export function LovePage(props: {
<Col
className={clsx(
'pb-[58px] lg:pb-0', // bottom bar padding
'text-ink-1000 mx-auto min-h-screen w-full max-w-[1440px] lg:grid lg:grid-cols-12'
'text-ink-1000 mx-auto min-h-screen w-full lg:grid lg:grid-cols-12'
)}
>
<Toaster
@@ -74,13 +74,13 @@ export function LovePage(props: {
) : (
<Sidebar
navigationOptions={desktopSidebarOptions}
className="sticky top-0 hidden self-start px-2 lg:col-span-2 lg:flex"
/>
className="sticky top-0 hidden self-start px-2 lg:col-span-2 lg:flex sidebar-nav bg-canvas-25"
/>
)}
<main
className={clsx(
'flex flex-1 flex-col lg:mt-6 xl:px-2',
'col-span-10',
'col-span-8',
className
)}
>
@@ -97,17 +97,23 @@ export function LovePage(props: {
)
}
const Profiles = { name: 'Profiles', href: '/', icon: SolidHomeIcon };
const ProfilesHome = { name: 'Profiles', href: '/', icon: HomeIcon };
const faq = { name: 'FAQ', href: '/faq', icon: SolidQuestionIcon };
const About = { name: 'About', href: '/about', icon: QuestionMarkCircleIcon };
const Signin = { name: 'Sign in', href: '/signin', icon: UserCircleIcon };
const Notifs = {name: 'Notifs', href: `/notifications`, icon: NotificationsIcon};
const NotifsSolid = {name: 'Notifs', href: `/notifications`, icon: SolidNotificationsIcon};
const Messages = {name: 'Messages', href: '/messages', icon: PrivateMessagesIcon};
function getBottomNavigation(user: User, lover: Lover | null | undefined) {
return buildArray(
{ name: 'Profiles', href: '/', icon: SolidHomeIcon },
{
name: 'Notifs',
href: `/notifications`,
icon: SolidNotificationsIcon,
},
Profiles,
NotifsSolid,
{
name: 'Profile',
href: lover === null ? '/signup' : `/${user.username}`,
icon: SolidHomeIcon,
},
{
name: 'Messages',
@@ -120,36 +126,32 @@ function getBottomNavigation(user: User, lover: Lover | null | undefined) {
}
const signedOutNavigation = () => [
{ name: 'Profiles', href: '/', icon: SolidHomeIcon },
{ name: 'About', href: '/about', icon: SolidQuestionIcon },
{ name: 'Sign in', href: '/signin', icon: UserCircleIcon },
Profiles,
About,
faq,
Signin,
]
const getDesktopNav = (user: User | null | undefined) => {
if (user)
return buildArray(
{ name: 'Profiles', href: '/', icon: HomeIcon },
{
name: 'Notifs',
href: `/notifications`,
icon: NotificationsIcon,
},
{
name: 'Messages',
href: '/messages',
icon: PrivateMessagesIcon,
},
{ name: 'About', href: '/about', icon: QuestionMarkCircleIcon }
ProfilesHome,
Notifs,
Messages,
About,
faq
)
return buildArray(
// { name: 'Profiles', href: '/', icon: HomeIcon },
{ name: 'About', href: '/about', icon: QuestionMarkCircleIcon }
About,
faq
)
}
// No sidebar when signed out
const getSidebarNavigation = (_toggleModal: () => void) => {
return buildArray(
{ name: 'About', href: '/about', icon: QuestionMarkCircleIcon }
About,
faq
)
}

View File

@@ -131,13 +131,13 @@ function RelationshipType(props: { lover: Lover }) {
const relationshipTypes = lover.pref_relation_styles
const seekingGenderText = stringOrStringArrayToText({
text: relationshipTypes.map((rel) =>
convertRelationshipType(rel as RelationshipType)
),
convertRelationshipType(rel as RelationshipType).toLowerCase()
).sort(),
preText: 'Seeking',
postText:
relationshipTypes.length == 1 && relationshipTypes[0] == 'mono'
? 'relationship'
: 'relationships',
// postText:
// relationshipTypes.length == 1 && relationshipTypes[0] == 'mono'
// ? 'relationship'
// : 'relationships',
asSentence: true,
capitalizeFirstLetterOption: false,
})

View File

@@ -12,7 +12,7 @@ export default function ManifoldLoveLogo(props: {
const inner = (
<>
<FavIcon className="dark:invert"/>
<div className={clsx('my-auto text-xl font-thin')}>
<div className={clsx('my-auto text-xl font-thin logo')}>
{ENV == 'DEV' ? 'Compass dev' : 'Compass'}
</div>
</>

View File

@@ -0,0 +1,43 @@
import {LovePage} from "web/components/love-page";
import {Col} from "web/components/layout/col";
import ReactMarkdown from "react-markdown";
import React from "react";
import Link from "next/link";
type Props = {
content: string;
filename: string;
};
const MarkdownLink = ({href, children}: { href?: string; children: React.ReactNode }) => {
if (!href) return <>{children}</>
// If href is internal, use Next.js Link
if (href.startsWith('/')) {
return <Link href={href}>{children}</Link>
}
// For external links, fall back to <a>
return (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
)
}
export default function MarkdownPage({content, filename}: Props) {
return (
<LovePage trackPageView={filename} className={'col-span-8'}>
<Col className="items-center">
<Col className='w-full rounded px-3 py-4 sm:px-6 space-y-4 customlink'>
<ReactMarkdown
components={{
a: ({node, children, ...props}) => <MarkdownLink {...props}>{children}</MarkdownLink>
}}
>{content}
</ReactMarkdown>
</Col>
</Col>
</LovePage>
);
}

View File

@@ -38,7 +38,7 @@ export function BottomNavBar(props: {
}
return (
<nav className="border-ink-200 dark:border-ink-300 text-ink-700 bg-canvas-0 fixed inset-x-0 bottom-0 z-50 flex select-none items-center justify-between border-t-2 text-xs lg:hidden">
<nav className="border-ink-200 dark:border-ink-300 text-ink-700 bg-canvas-50 fixed inset-x-0 bottom-0 z-50 flex select-none items-center justify-between border-t-2 text-xs lg:hidden sidebar-nav">
{navigationOptions.map((item) => (
<NavBarItem
key={item.name}
@@ -149,8 +149,7 @@ function NavBarItem(props: {
}
const currentBasePath = '/' + (currentPage?.split('/')[1] ?? '')
const isCurrentPage =
item.href != null && currentBasePath === item.href.split('?')[0]
const isCurrentPage = currentBasePath === item.href.split('?')[0]
return (
<Link
@@ -207,7 +206,7 @@ export function MobileSidebar(props: {
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>
<div className="bg-canvas-0 relative flex w-full max-w-xs flex-1 flex-col">
<div className="bg-canvas-25 relative flex w-full max-w-[200px] flex-1 flex-col">
<div className="mx-2 h-0 flex-1 overflow-y-auto">
<Sidebar
navigationOptions={sidebarNavigationOptions}

View File

@@ -28,6 +28,8 @@ import {City, CityRow, loverToCity, useCitySearch} from "web/components/search-l
import {AddPhotosWidget} from './widgets/add-photos'
import {RadioToggleGroup} from "web/components/widgets/radio-toggle-group";
import {MultipleChoiceOptions} from "common/love/multiple-choice";
import {RELATIONSHIP_CHOICES} from "web/components/filters/choices";
import toast from "react-hot-toast";
export const OptionalLoveUserForm = (props: {
lover: LoverRow
@@ -68,6 +70,10 @@ export const OptionalLoveUserForm = (props: {
)
if (error) {
console.error(error)
toast.error(
`We ran into an issue saving your profile. Please try again or contact us if the issue persists.`
)
setIsSubmitting(false)
return
}
if (!isEqual(newLinks, user.link)) {
@@ -225,7 +231,7 @@ export const OptionalLoveUserForm = (props: {
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Aged between</label>
<label className={clsx(labelClassName)}>Who are aged between</label>
<Row className={'gap-2'}>
<Col>
<span>Min</span>
@@ -262,6 +268,17 @@ export const OptionalLoveUserForm = (props: {
</Row>
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Connection type</label>
<MultiCheckbox
choices={RELATIONSHIP_CHOICES}
selected={lover['pref_relation_styles']}
onChange={(selected) =>
setLover('pref_relation_styles', selected)
}
/>
</Col>
<Col className={clsx(colClassName, 'pb-4')}>
<label className={clsx(labelClassName)}>Socials</label>
@@ -522,7 +539,7 @@ export const OptionalLoveUserForm = (props: {
{lookingRelationship && <>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>
You want to have kids
I would like to have kids
</label>
<RadioToggleGroup
className={'w-44'}
@@ -533,22 +550,6 @@ export const OptionalLoveUserForm = (props: {
currentChoice={lover.wants_kids_strength ?? -1}
/>
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Relationship style</label>
<MultiCheckbox
choices={{
Monogamous: 'mono',
Polyamorous: 'poly',
'Open Relationship': 'open',
Other: 'other',
}}
selected={lover['pref_relation_styles']}
onChange={(selected) =>
setLover('pref_relation_styles', selected)
}
/>
</Col>
</>}
<Col className={clsx(colClassName)}>

View File

@@ -2,14 +2,16 @@ import {Lover} from 'common/love/lover'
import {CompatibilityScore} from 'common/love/compatibility-score'
import {LoadingIndicator} from 'web/components/widgets/loading-indicator'
import {LoadMoreUntilNotVisible} from 'web/components/widgets/visibility-observer'
import {useUser} from 'web/hooks/use-user'
import {track} from 'web/lib/service/analytics'
import {Col} from './layout/col'
import clsx from 'clsx'
import {JSONContent} from "@tiptap/core";
import {Content} from "web/components/widgets/editor";
import React from "react";
import Router from "next/router";
import Link from "next/link";
import {Row} from "web/components/layout/row";
import {CompatibleBadge} from "web/components/widgets/compatible-badge";
import {useUser} from "web/hooks/use-user";
export const ProfileGrid = (props: {
lovers: Lover[]
@@ -30,6 +32,8 @@ export const ProfileGrid = (props: {
refreshStars,
} = props
const user = useUser()
return (
<div className="relative">
<div
@@ -38,7 +42,9 @@ export const ProfileGrid = (props: {
isReloading && 'animate-pulse opacity-80'
)}
>
{lovers.map((lover) => (
{lovers
.filter((lover) => lover.user_id !== user?.id)
.map((lover) => (
<ProfilePreview
key={lover.id}
lover={lover}
@@ -75,15 +81,13 @@ function ProfilePreview(props: {
// const currentUser = useUser()
return (
<div
onClick={() => {
track('click love profile preview')
Router.push(`/${user.username}`)
}}
className="cursor-pointer group block dark:bg-gray-800 rounded-lg overflow-hidden shadow hover:shadow-md transition-shadow duration-200 h-full"
<Link
onClick={() => track('click love profile preview')}
href={`/${user.username}`}
className="cursor-pointer group block bg-canvas-100 rounded-lg overflow-hidden shadow hover:shadow-md transition-shadow duration-200 h-full"
>
<Col
className="relative h-40 w-full overflow-hidden rounded text-white transition-all hover:scale-y-105 hover:drop-shadow">
className="relative h-40 w-full overflow-hidden rounded transition-all hover:scale-y-105 hover:drop-shadow">
{/*{pinned_url ? (*/}
{/* <Image*/}
{/* src={pinned_url}*/}
@@ -100,22 +104,23 @@ function ProfilePreview(props: {
{/* </Col>*/}
{/*)}*/}
{/*<Row className="absolute inset-x-0 right-0 top-0 items-start justify-between bg-gradient-to-b from-black/70 via-black/70 to-transparent px-2 pb-3 pt-2">*/}
{/* {currentUser ? (*/}
{/* <StarButton*/}
{/* className="!pt-0"*/}
{/* isStarred={hasStar}*/}
{/* refresh={refreshStars}*/}
{/* targetLover={lover}*/}
{/* hideTooltip*/}
{/* />*/}
{/* ) : (*/}
{/* <div />*/}
{/* )}*/}
{/* {compatibilityScore && (*/}
{/* <CompatibleBadge compatibility={compatibilityScore} />*/}
{/* )}*/}
{/*</Row>*/}
<Row
className="absolute top-2 right-2 items-start justify-end px-2 pb-3">
{/* {currentUser ? (*/}
{/* <StarButton*/}
{/* className="!pt-0"*/}
{/* isStarred={hasStar}*/}
{/* refresh={refreshStars}*/}
{/* targetLover={lover}*/}
{/* hideTooltip*/}
{/* />*/}
{/* ) : (*/}
{/* <div />*/}
{/* )}*/}
{compatibilityScore && (
<CompatibleBadge compatibility={compatibilityScore}/>
)}
</Row>
<Col className="absolute inset-x-0 bottom-0 bg-gradient-to-t to-transparent px-4 pb-2 pt-6">
<div>
@@ -125,6 +130,7 @@ function ProfilePreview(props: {
{user.name}
</h3>
<div className="text-sm text-gray-500 dark:text-gray-400">
{/*TODO: fix nested <a> links warning (one from Link above, one from link in bio below)*/}
<Content className="w-full line-clamp-4" content={lover.bio as JSONContent}/>
</div>
{/*{age}*/}
@@ -135,6 +141,6 @@ function ProfilePreview(props: {
{/*</Row>*/}
</Col>
</Col>
</div>
</Link>
)
}

View File

@@ -1,26 +1,25 @@
import { PencilIcon, EyeIcon, LockClosedIcon } from '@heroicons/react/outline'
import { DotsHorizontalIcon } from '@heroicons/react/outline'
import {DotsHorizontalIcon, EyeIcon, LockClosedIcon, PencilIcon} from '@heroicons/react/outline'
import clsx from 'clsx'
import Router from 'next/router'
import Link from 'next/link'
import { User } from 'common/user'
import { Button } from 'web/components/buttons/button'
import { MoreOptionsUserButton } from 'web/components/buttons/more-options-user-button'
import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
import { SendMessageButton } from 'web/components/messaging/send-message-button'
import {User} from 'common/user'
import {Button} from 'web/components/buttons/button'
import {MoreOptionsUserButton} from 'web/components/buttons/more-options-user-button'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
import {SendMessageButton} from 'web/components/messaging/send-message-button'
import LoverPrimaryInfo from './lover-primary-info'
import { OnlineIcon } from '../online-icon'
import { track } from 'web/lib/service/analytics'
import {OnlineIcon} from '../online-icon'
import {track} from 'web/lib/service/analytics'
import DropdownMenu from 'web/components/comments/dropdown-menu'
import { ShareProfileButton } from '../widgets/share-profile-button'
import { Lover } from 'common/love/lover'
import { useUser } from 'web/hooks/use-user'
import { linkClass } from 'web/components/widgets/site-link'
import { StarButton } from '../widgets/star-button'
import { api, updateLover } from 'web/lib/api'
import { useState } from 'react'
import { VisibilityConfirmationModal } from './visibility-confirmation-modal'
import {ShareProfileButton} from '../widgets/share-profile-button'
import {Lover} from 'common/love/lover'
import {useUser} from 'web/hooks/use-user'
import {linkClass} from 'web/components/widgets/site-link'
import {StarButton} from '../widgets/star-button'
import {api, updateLover} from 'web/lib/api'
import React, {useState} from 'react'
import {VisibilityConfirmationModal} from './visibility-confirmation-modal'
export default function LoverProfileHeader(props: {
user: User
@@ -44,7 +43,7 @@ export default function LoverProfileHeader(props: {
const isCurrentUser = currentUser?.id === user.id
const [showVisibilityModal, setShowVisibilityModal] = useState(false)
console.log('LoverProfileHeader', { user, lover, currentUser })
console.log('LoverProfileHeader', {user, lover, currentUser})
return (
<Col className="w-full">
@@ -52,7 +51,7 @@ export default function LoverProfileHeader(props: {
<Row className="items-center gap-1">
<Col className="gap-1">
<Row className="items-center gap-1 text-xl">
<OnlineIcon last_online_time={lover.last_online_time} />
<OnlineIcon last_online_time={lover.last_online_time}/>
<span>
{simpleView ? (
<Link className={linkClass} href={`/${user.username}`}>
@@ -64,7 +63,7 @@ export default function LoverProfileHeader(props: {
, {lover.age}
</span>
</Row>
<LoverPrimaryInfo lover={lover} />
<LoverPrimaryInfo lover={lover}/>
</Col>
</Row>
{currentUser && isCurrentUser ? (
@@ -81,13 +80,13 @@ export default function LoverProfileHeader(props: {
}}
size="sm"
>
<PencilIcon className=" h-4 w-4" />
<PencilIcon className=" h-4 w-4"/>
</Button>
<DropdownMenu
menuWidth={'w-52'}
icon={
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true"/>
}
items={[
{
@@ -97,9 +96,9 @@ export default function LoverProfileHeader(props: {
: 'Limit to Members Only',
icon:
lover.visibility === 'member' ? (
<EyeIcon className="h-4 w-4" />
<EyeIcon className="h-4 w-4"/>
) : (
<LockClosedIcon className="h-4 w-4" />
<LockClosedIcon className="h-4 w-4"/>
),
onClick: () => setShowVisibilityModal(true),
},
@@ -112,7 +111,7 @@ export default function LoverProfileHeader(props: {
)
if (confirmed) {
track('delete love profile')
await api('me/delete', { username: user.username })
await api('me/delete', {username: user.username})
window.location.reload()
}
},
@@ -122,8 +121,10 @@ export default function LoverProfileHeader(props: {
</Row>
) : (
<Row className="items-center gap-1 sm:gap-2">
{/*TODO: Add score to profile page once we can efficiently compute it (i.e., not recomputing it for every profile)*/}
{/*<CompatibleBadge compatibility={compatibilityScore}/>*/}
<ShareProfileButton
className="hidden sm:flex"
className="sm:flex"
username={user.username}
/>
{currentUser && (
@@ -134,16 +135,16 @@ export default function LoverProfileHeader(props: {
/>
)}
{currentUser && showMessageButton && (
<SendMessageButton toUser={user} currentUser={currentUser} />
<SendMessageButton toUser={user} currentUser={currentUser}/>
)}
<MoreOptionsUserButton user={user} />
<MoreOptionsUserButton user={user}/>
</Row>
)}
</Row>
<Row className="justify-end sm:hidden">
<ShareProfileButton username={user.username} />
</Row>
{/*<Row className="justify-end sm:hidden">*/}
{/* <ShareProfileButton username={user.username} />*/}
{/*</Row>*/}
<VisibilityConfirmationModal
open={showVisibilityModal}
@@ -152,7 +153,7 @@ export default function LoverProfileHeader(props: {
onConfirm={async () => {
const newVisibility =
lover.visibility === 'member' ? 'public' : 'member'
await updateLover({ visibility: newVisibility })
await updateLover({visibility: newVisibility})
refreshLover()
}}
/>

View File

@@ -20,6 +20,9 @@ import { LikeData, ShipData } from 'common/api/love-types'
import { useAPIGetter } from 'web/hooks/use-api-getter'
import { useGetter } from 'web/hooks/use-getter'
import { getStars } from 'web/lib/supabase/stars'
import {Content} from "web/components/widgets/editor";
import {JSONContent} from "@tiptap/core";
import React from "react";
export function LoverProfile(props: {
lover: Lover
@@ -63,6 +66,8 @@ export function LoverProfile(props: {
const showMessageButton = liked || likedBack || !areCompatible
const isProfileVisible = currentUser || lover.visibility === 'public'
return (
<>
<LoverProfileHeader
@@ -74,7 +79,7 @@ export function LoverProfile(props: {
showMessageButton={showMessageButton}
refreshLover={refreshLover}
/>
{currentUser || lover.visibility === 'public' ? (
{isProfileVisible ? (
<LoverContent
user={user}
lover={lover}
@@ -88,6 +93,9 @@ export function LoverProfile(props: {
/>
) : (
<Col className="bg-canvas-0 w-full gap-4 rounded p-4">
<div className="text-sm text-gray-500 dark:text-gray-400">
<Content className="w-full line-clamp-6" content={lover.bio as JSONContent}/>
</div>
<Col className="relative gap-4">
<div className="bg-ink-200 dark:bg-ink-400 h-4 w-2/5" />
<div className="bg-ink-200 dark:bg-ink-400 h-4 w-3/5" />
@@ -102,7 +110,7 @@ export function LoverProfile(props: {
{areCompatible &&
((!fromLoverPage && !isCurrentUser) ||
(fromLoverPage && fromLoverPage.user_id === currentUser?.id)) && (
<Row className="sticky bottom-[70px] right-0 mr-1 self-end lg:bottom-6">
<Row className="right-0 mr-1 self-end lg:bottom-6">
<LikeButton targetLover={lover} liked={liked} refresh={refresh} />
</Row>
)}
@@ -118,7 +126,7 @@ export function LoverProfile(props: {
/>
</Row>
)}
{lover.photo_urls && <ProfileCarousel lover={lover} />}
{isProfileVisible && lover.photo_urls && <ProfileCarousel lover={lover} />}
</>
)
}

View File

@@ -53,7 +53,11 @@ export function ProfilesHome() {
if (!user) return;
setIsReloading(true);
const current = ++id.current;
api('get-lovers', removeNullOrUndefinedProps({limit: 20, compatibleWithUserId: user?.id, ...filters}) as any)
api('get-lovers', removeNullOrUndefinedProps({
limit: 20,
compatibleWithUserId: user?.id,
...filters
}) as any)
.then(({lovers}) => {
if (current === id.current) setLovers(lovers);
})
@@ -74,7 +78,8 @@ export function ProfilesHome() {
const result = await api('get-lovers', removeNullOrUndefinedProps({
limit: 20,
compatibleWithUserId: user?.id,
after: lastLover?.id.toString(), ...filters
after: lastLover?.id.toString(),
...filters
}) as any);
if (result.lovers.length === 0) return false;
setLovers((prev) => (prev ? [...prev, ...result.lovers] : result.lovers));

View File

@@ -71,7 +71,7 @@ export function SelectUsers(props: {
id="user name"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="e.g. Ian Philips"
placeholder="Search users..."
/>
</Col>
{queryReady && (

View File

@@ -10,11 +10,11 @@ import {
TbBrandInstagram,
TbBrandLinkedin,
TbBrandMastodon,
// TbBrandOnlyfans,
TbBrandPatreon,
TbBrandPaypal,
TbBrandSpotify,
TbBrandX,
} from 'react-icons/tb'
import Foldy from 'web/public/manifold-logo.svg'
export const PLATFORM_ICONS: {
[key in Site]: (props: { className?: string }) => ReactNode
@@ -25,15 +25,21 @@ export const PLATFORM_ICONS: {
bluesky: TbBrandBluesky,
mastodon: TbBrandMastodon,
substack: LuBookmark,
// onlyfans: TbBrandOnlyfans,
instagram: TbBrandInstagram,
github: TbBrandGithub,
linkedin: TbBrandLinkedin,
facebook: TbBrandFacebook,
spotify: TbBrandSpotify,
patreon: TbBrandPatreon,
paypal: TbBrandPaypal,
}
export const SocialIcon = (props: { site: string; className?: string }) => {
export const SocialIcon = (props: {
site: string;
className?: string;
size?: number;
color?: string;
}) => {
const { site, ...rest } = props
const Icon = PLATFORM_ICONS[site as Site] || PLATFORM_ICONS.site

View File

@@ -69,7 +69,7 @@ export const LikeButton = (props: {
<div className="p-2 pb-0 pt-0">{liked ? <>Liked!</> : <>Like</>}</div>
</Col>
</button>
<LikeConfimationDialog
<LikeConfirmationDialog
targetLover={targetLover}
hasFreeLike={hasFreeLike}
submit={like}
@@ -86,7 +86,7 @@ export const LikeButton = (props: {
)
}
const LikeConfimationDialog = (props: {
const LikeConfirmationDialog = (props: {
targetLover: Lover
hasFreeLike: boolean
open: boolean

View File

@@ -19,6 +19,7 @@ export function useNearbyCities(
cityId: referenceCityId,
radius,
}).then((result) => {
console.log('search-near-city', result)
if (thisSearchCount == searchCount.current) {
if (result.status === 'failure') {
setNearbyCities(null)

View File

@@ -1,11 +1,7 @@
export type RelationshipType = 'mono' | 'poly' | 'open' | 'other'
import {REVERTED_RELATIONSHIP_CHOICES} from "web/components/filters/choices";
export type RelationshipType = keyof typeof REVERTED_RELATIONSHIP_CHOICES
export function convertRelationshipType(relationshipType: RelationshipType) {
if (relationshipType == 'mono') {
return 'monogamous'
}
if (relationshipType == 'poly') {
return 'polyamorous'
}
return relationshipType
return REVERTED_RELATIONSHIP_CHOICES[relationshipType]
}

View File

@@ -1,6 +1,6 @@
{
"name": "web",
"version": "0.0.0",
"version": "1.0.0",
"private": true,
"scripts": {
"serve": "next dev -p 3000",

View File

@@ -117,7 +117,7 @@ function UserPageInner(props: ActiveUserPageProps) {
const {lover: clientLover, refreshLover} = useLoverByUser(user)
// Show previous profile while loading another one
const lover = clientLover ?? staticLover
console.log('lover:', user?.username, lover, clientLover, staticLover)
// console.log('lover:', user?.username, lover, clientLover, staticLover)
return (
<LovePage

View File

@@ -100,11 +100,15 @@ function MyApp({ Component, pageProps }: AppProps<ManifoldPageProps>) {
name="viewport"
content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no"
/>
<link
href="https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
</Head>
<PostHogProvider client={posthog}>
<div
className={clsx(
'font-figtree contents font-normal',
'contents font-normal',
logoFont.variable,
mainFont.variable
)}
@@ -118,7 +122,7 @@ function MyApp({ Component, pageProps }: AppProps<ManifoldPageProps>) {
</div>
</div>
</PostHogProvider>
{/* LOVE TODO: Reenable one tap setup */}
{/* TODO: Reenable one tap setup */}
{/* <GoogleOneTapSetup /> */}
</>
)

View File

@@ -9,7 +9,7 @@ export default function Document() {
<link rel="icon" href={ENV_CONFIG.faviconPath} />
<Script src="/init-theme.js" strategy="beforeInteractive" />
</Head>
<body className="bg-canvas-50 text-ink-1000">
<body className="body-bg text-ink-1000">
<Main />
<NextScript />
</body>

View File

@@ -10,7 +10,7 @@ export const AboutBlock = (props: {
const {text, title} = props
return <section className="mb-12">
<h2 className="text-3xl font-bold mb-4">{title}</h2>
<p className="text-lg text-gray-500">{text}</p>
<p className="text-lg">{text}</p>
</section>;
}
@@ -59,8 +59,7 @@ export default function About() {
<AboutBlock
title="Democratic"
text={<span>Governed by the community, while ensuring no drift through our <Link href="/constitution"
className="text-blue-600 dark:text-blue-400">constitution</Link>.</span>}
text={<span className="customlink">Governed by the community, while ensuring no drift through our <Link href="/constitution">constitution</Link>.</span>}
/>
<AboutBlock

View File

@@ -1,27 +1,19 @@
import React from 'react';
import ReactMarkdown from 'react-markdown';
import {LovePage} from "web/components/love-page";
import {Col} from "web/components/layout/col";
import fs from 'fs';
import path from 'path';
import MarkdownPage from "web/components/markdown";
const FILENAME = 'constitution';
export async function getStaticProps() {
const filePath = path.join(process.cwd(), 'public', 'md', 'constitution.md');
const filePath = path.join(process.cwd(), 'public', 'md', FILENAME + '.md');
const content = fs.readFileSync(filePath, 'utf8');
return { props: { content } };
return {props: {content}};
}
type Props = { content: string };
export default function Constitution({ content }: Props) {
return (
<LovePage trackPageView={'test'}>
<Col className="items-center">
<Col className={'bg-canvas-0 w-full rounded px-3 py-4 sm:px-6 space-y-4'}>
<ReactMarkdown>{content}</ReactMarkdown>
</Col>
</Col>
</LovePage>
);
export default function Faq({content}: Props) {
return <MarkdownPage content={content} filename={FILENAME}></MarkdownPage>
}

19
web/pages/faq.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from 'react';
import fs from 'fs';
import path from 'path';
import MarkdownPage from "web/components/markdown";
const FILENAME = 'faq';
export async function getStaticProps() {
const filePath = path.join(process.cwd(), 'public', 'md', FILENAME + '.md');
const content = fs.readFileSync(filePath, 'utf8');
return {props: {content}};
}
type Props = { content: string };
export default function Faq({content}: Props) {
return <MarkdownPage content={content} filename={FILENAME}></MarkdownPage>
}

19
web/pages/financials.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from 'react';
import fs from 'fs';
import path from 'path';
import MarkdownPage from "web/components/markdown";
const FILENAME = 'financials';
export async function getStaticProps() {
const filePath = path.join(process.cwd(), 'public', 'md', FILENAME + '.md');
const content = fs.readFileSync(filePath, 'utf8');
return {props: {content}};
}
type Props = { content: string };
export default function Faq({content}: Props) {
return <MarkdownPage content={content} filename={FILENAME}></MarkdownPage>
}

View File

@@ -12,7 +12,7 @@ export default function ProfilesPage() {
return (
<LovePage trackPageView={'user profiles'}>
<Col className="items-center">
<Col className={'bg-canvas-0 w-full rounded px-3 py-4 sm:px-6'}>
<Col className={'w-full rounded px-3 py-4 sm:px-6'}>
{user ? <ProfilesHome/> : <LoggedOutHome/>}
</Col>
</Col>

View File

@@ -0,0 +1,29 @@
import React from 'react';
import fs from 'fs';
import path from 'path';
import MarkdownPage from "web/components/markdown";
type Props = {
content: string;
filename: string;
};
export default function Markdown({content, filename}: Props) {
return <MarkdownPage content={content} filename={filename}></MarkdownPage>
}
export async function getStaticPaths() {
const mdDir = path.join(process.cwd(), 'public', 'md');
const files = fs.readdirSync(mdDir);
const paths = files.map((file) => ({
params: {filename: file.replace(/\.md$/, '')},
}));
return {paths, fallback: false};
}
export async function getStaticProps({params}: { params: { filename: string } }) {
const filePath = path.join(process.cwd(), 'public', 'md', `${params.filename}.md`);
const content = fs.readFileSync(filePath, 'utf8');
return {props: {content, filename: params.filename}};
}

19
web/pages/members.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from 'react';
import fs from 'fs';
import path from 'path';
import MarkdownPage from "web/components/markdown";
const FILENAME = 'members';
export async function getStaticProps() {
const filePath = path.join(process.cwd(), 'public', 'md', FILENAME + '.md');
const content = fs.readFileSync(filePath, 'utf8');
return {props: {content}};
}
type Props = { content: string };
export default function Faq({content}: Props) {
return <MarkdownPage content={content} filename={FILENAME}></MarkdownPage>
}

View File

@@ -6,7 +6,7 @@ export default function PrivacyPage() {
return (
<LovePage
trackPageView={'terms'}
className="max-w-4xl mx-auto p-8"
className="max-w-4xl mx-auto p-8 col-span-8 bg-canvas-0"
>
<h1 className="text-3xl font-semibold text-center mb-6">Privacy Policy</h1>

View File

@@ -14,6 +14,7 @@ import {getLoverRow} from "common/love/lover";
import {db} from "web/lib/supabase/db";
import Router from "next/router";
import {useUser} from "web/hooks/use-user";
import {GoogleButton} from "web/components/buttons/sign-up-button";
export default function RegisterPage() {
@@ -109,7 +110,7 @@ function RegisterComponent() {
return (
<LovePage trackPageView={'register'}>
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<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">
{registrationSuccess ? (
<div className="text-center">
@@ -164,7 +165,7 @@ function RegisterComponent() {
<div className="flex justify-center mb-6">
<FavIcon className="dark:invert"/>
</div>
<h2 className="text-center text-3xl font-extrabold ">
<h2 className="mt-6 text-center text-3xl font-extrabold ">
Get Started
</h2>
</div>
@@ -179,7 +180,7 @@ function RegisterComponent() {
name="email"
type="email"
required
className="bg-primary-50 appearance-none rounded-none 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"
className="bg-canvas-50 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Email"
/>
</div>
@@ -192,20 +193,20 @@ function RegisterComponent() {
name="password"
type="password"
required
className="bg-primary-50 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"
className="bg-canvas-50 bg-input 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>
<div>
<p className="text-xs mt-2 text-center">
<p className="text-sm mt-2 text-center customlink">
By signing up, I agree to the{" "}
<Link href="/terms" className="underline hover:text-blue-600">
<Link href="/terms">
Terms and Conditions
</Link>{" "}
and{" "}
<Link href="/privacy" className="underline hover:text-blue-600">
<Link href="/privacy">
Privacy Policy
</Link>.
</p>
@@ -215,7 +216,7 @@ function RegisterComponent() {
<div className="text-red-500 text-sm text-center">{error}</div>
)}
<div className="space-y-2">
<div className="space-y-4">
<button
type="submit"
disabled={isLoading}
@@ -229,25 +230,17 @@ function RegisterComponent() {
<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 sign up with</span>
<span className="px-2 body-bg text-gray-500">Or sign up with</span>
</div>
</div>
<button
type="button"
onClick={signupThenMaybeRedirectToSignup}
disabled={isLoading}
className="w-full flex items-center justify-center gap-2 py-2 px-4 border border-gray-300 rounded-full shadow-sm text-sm font-medium 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"/>
Google
</button>
<GoogleButton onClick={signupThenMaybeRedirectToSignup} isLoading={isLoading} />
</div>
</form>
<div className="text-center text-sm mt-2">
<div className="my-8" />
<div className="text-center customlink">
<p className="">
Already have an account?{' '}
<Link href="/signin" className="font-medium text-blue-600 hover:text-blue-500">
<Link href="/signin">
Sign in
</Link>
</p>

View File

@@ -3,7 +3,6 @@
import {useSearchParams} from "next/navigation";
import {Suspense, useEffect, useState} from "react";
import Link from "next/link";
import {FcGoogle} from "react-icons/fc";
import {auth, firebaseLogin} from "web/lib/firebase/users";
import FavIcon from "web/public/FavIcon";
@@ -13,6 +12,7 @@ import {db} from "web/lib/supabase/db";
import Router from "next/router";
import {LovePage} from "web/components/love-page";
import {useUser} from "web/hooks/use-user";
import {GoogleButton} from "web/components/buttons/sign-up-button";
export default function LoginPage() {
return (
@@ -64,6 +64,10 @@ function RegisterComponent() {
setError(null);
try {
const creds = await firebaseLogin();
if (creds){
setIsLoading(true)
setIsLoadingGoogle(true);
}
} catch (error) {
console.error("Error signing in:", error);
const message = 'Failed to sign in with Google';
@@ -134,7 +138,7 @@ function RegisterComponent() {
name="email"
type="email"
required
className="bg-primary-50 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
className="bg-canvas-50 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Email"
/>
</div>
@@ -147,7 +151,7 @@ function RegisterComponent() {
name="password"
type="password"
required
className="bg-primary-50 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
className="bg-canvas-50 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-b-md border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Password"
/>
</div>
@@ -171,25 +175,19 @@ function RegisterComponent() {
<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>
<span className="px-2 body-bg 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-full shadow-sm text-sm font-medium 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"/>
Google
</button>
<GoogleButton onClick={handleGoogleSignIn} isLoading={isLoading}/>
</div>
</form>
<div className="text-center">
<Link href="/register" className="text-blue-600 hover:underline">
No account? Register.
</Link>
<div className="text-center customlink">
<p className="">
No account?{' '}
<Link href="/register">
Register
</Link>
</p>
</div>
</div>
</div>

View File

@@ -6,7 +6,7 @@ export default function TermsPage() {
return (
<LovePage
trackPageView={'terms'}
className="max-w-4xl mx-auto p-8 text-gray-800 dark:text-white"
className="max-w-4xl mx-auto p-8 text-gray-800 dark:text-white col-span-8 bg-canvas-0"
>
<h1 className="text-3xl font-semibold text-center mb-6">Terms & Conditions</h1>

View File

@@ -27,12 +27,13 @@ We, the community of Compass, commit to building and maintaining this project in
- Sustained inactivity (e.g., less than 10 hours of contribution for 12 months).
- Proven bad-faith conduct (vote manipulation, harassment, sabotage). Removal requires a **2/3 vote** of Members.
[Current members and administrators](/members)
## Article III: Governance Structure
### Section 1: Interim Governance
- Until the community reaches **5 voting members**, governance decisions shall be made by **unanimous agreement** of the *Founding Maintainers*.
- The Founding Maintainers may appoint temporary coordinators for specific tasks.
- Until the community reaches **5 voting members**, governance decisions shall be made by the *Founding Maintainer*.
- The Founding Maintainer may appoint temporary coordinators for specific tasks.
- Once the community reaches **5 voting members**, leadership positions will be filled via community election as described below.
### Section 2: Democratic Governance
@@ -81,4 +82,4 @@ If the project is dissolved, the platform will be shut down and made unavailable
*Adopted on: August 11, 2025*
*Founding Maintainers: Martin, Emily*
*Founding Maintainer: Martin Braquet*

96
web/public/md/faq.md Normal file
View File

@@ -0,0 +1,96 @@
# FAQ Compass
### What is Compass?
Compass is a **free, open-source platform to help people form deep, meaningful, and lasting connections** — whether platonic, romantic, or collaborative.
Unlike typical apps, Compass prioritizes **values, interests, and personality over swipes and ads**, giving you full control over who you discover and how you connect.
### Who is Compass for?
Anyone who wants more than small talk or casual networking. If you value **depth over quantity** and want relationships grounded in **shared values, trust, and understanding**, Compass is for you.
### Why is Compass different from other meeting apps?
* **Keyword Search**: Find people who share your niche interests (e.g., “Minimalism”, “Thinking, Fast and Slow”, “Indie film”).
* **Transparent Database**: See all profiles, apply filters, and search freely — no hidden algorithms.
* **Notification System**: Get alerts when new people match your searches — no endless scrolling required.
* **Personality-Centered**: Values and ideas first. Photos stay secondary.
* **Democratic & Open Source**: Built by the community, for the community — no ads, no hidden monetization.
### Is Compass for dating or friendship?
Both. You can specify whether youre looking for **platonic, romantic, or collaborative connections**.
### Who started Compass?
Compass was founded by [Martin Braquet](https://www.martinbraquet.com), an engineer and researcher passionate about tackling humanitys most pressing challenges — from climate change and AI safety to animal welfare.
Martin has lived across Europe, the U.S., India, and Indonesia, immersing himself in diverse practices ranging from meditation retreats to sustainability-focused forest co-ops. These experiences shaped his conviction that deep one-to-one human connections are among the most meaningful drivers of well-being and positive change.
Compass grew out of that conviction. While Martin has long been driven to reduce global risks and suffering, he also recognized that his own life — and the lives of many others — would be greatly enriched by more profound, close, and supportive relationships. Compass is his attempt to build an open, transparent, and community-driven platform where people can connect around shared values, curiosity, and care, without the distractions of swipes, ads, or superficiality.
Martin continues to serve as an initiator and steward of Compass, but its direction is intentionally placed in the hands of the community through the Compass Constitution (as detailed in the next section).
### How does governance work?
Compass is run democratically under a [constitution](/constitution) that prevents central control and ensures long-term alignment with its mission.
* Major decisions (scope, funding, rules) are voted on by **active contributors**.
* The full constitution is **public and transparent**.
* No corporate capture — Compass will always remain a community-owned project.
### Is Compass really free?
Yes. Compass will always be:
* **Ad-free**
* **Subscription-free**
* **Open-source**
Supported entirely by **donations**, not by selling your data or attention.
### How do you sustain Compass without ads or subscriptions?
Through **donations from the community**. Options include:
* Patreon
* PayPal
* GitHub Sponsors
All funding and expenses are **publicly documented** [here](/financials).
### Is my data safe?
Yes.
* Your data will **never be sold**.
* You can **control what is visible publicly**.
* Messaging may move toward **end-to-end encryption** in future versions.
### How is the compatibility score calculated?
The **compatibility score** comes from answers to **compatibility prompts**. Each user provides:
* **Their answer**
* **Answers they would accept from others**
* **A degree of importance** for each question
Matches are scored based on how well two peoples responses and accepted answers align, weighted by importance.
The [full implementation](https://github.com/CompassMeet/Compass/blob/main/common/src/love/compatibility-score.ts) is **open source** and open to review, feedback, and improvement by the community.
### How can I help?
* **Give Feedback**: [Fill out the suggestion form](https://forms.gle/tKnXUMAbEreMK6FC6)
* **Join the Discussion**: [Discord Community](https://discord.gg/8Vd7jzqjun)
* **Contribute to Development**: [View the code on GitHub](https://github.com/CompassMeet/Compass)
* **Donate**: [Support infrastructure](/about)
* **Spread the Word**: Tell friends and family who value depth and real connection.
### Whats next?
Compass has officially **launched**. The platform is now open for everyone who values meaningful, values-driven connections. Our focus has shifted toward **growing the community** and **securing donations** to sustain and expand the platform.
Every action, whether sharing, donating, or contributing, directly helps Compass remain **ad-free, subscription-free, and community-owned**.

View File

@@ -0,0 +1,20 @@
# Financials
See [this spreadsheet](https://docs.google.com/spreadsheets/d/18GJr-xSi_ypkgQIxfwPTMaKgQsfLLTjrZBtYd-TeGbc/edit?usp=sharing) for the most updated information.
### Expenses
- Hosting & Infrastructure: $100
- Marketing: $0
- Miscellaneous: $0
### Funding Sources
- Donations: $0
- Grants: $0
### Financial Summary
- Total Income: $0
- Total Expenses: $100
- Net Surplus: -$100

11
web/public/md/members.md Normal file
View File

@@ -0,0 +1,11 @@
# Current Members and Administrators
See the [full constitution](/constitution) for details on membership criteria and governance structure.
### Members
- Martin Braquet
### Administrators
- Martin Braquet

View File

@@ -2,153 +2,168 @@
@tailwind components;
@tailwind utilities;
.logo {
font-family: "Crimson Pro", Georgia, "Times New Roman", Times, serif;
}
@layer base {
html {
-webkit-tap-highlight-color: transparent;
}
:root {
/* Text / Ink Grey Scale */
--color-ink-0: 255 255 255; /* white */
--color-ink-50: 245 245 245;
--color-ink-100: 230 230 230;
--color-ink-200: 200 200 200;
--color-ink-300: 170 170 170;
--color-ink-400: 140 140 140;
--color-ink-500: 110 110 110;
--color-ink-600: 85 85 85;
--color-ink-700: 60 60 60;
--color-ink-800: 40 40 40;
--color-ink-900: 25 25 25;
--color-ink-950: 12 12 12;
--color-ink-1000: 0 0 0; /* black */
body {
font-family: "Crimson Pro", Georgia, "Times New Roman", Times, serif;
}
/* Background / Canvas Grey Scale */
--color-canvas-0: 255 255 255; /* white */
--color-canvas-50: 245 245 245;
--color-canvas-100: 230 230 230;
--color-canvas-200: 210 210 210;
--color-canvas-300: 190 190 190;
--color-canvas-400: 160 160 160;
--color-canvas-500: 130 130 130;
--color-canvas-600: 100 100 100;
--color-canvas-700: 75 75 75;
--color-canvas-800: 50 50 50;
--color-canvas-900: 30 30 30;
--color-canvas-950: 15 15 15;
--color-canvas-1000: 0 0 0; /* black */
button, input, label {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
--color-primary-950: 23 37 84; /* very dark navy */
--color-primary-900: 30 58 138;
--color-primary-800: 30 64 175;
--color-primary-700: 29 78 216;
--color-primary-600: 37 99 235;
--color-primary-500: 59 130 246; /* standard blue */
--color-primary-400: 96 165 250;
--color-primary-300: 147 197 253;
--color-primary-200: 191 219 254;
--color-primary-100: 219 234 254;
--color-primary-50: 239 246 255; /* very light blue */
html {
-webkit-tap-highlight-color: transparent;
}
--color-yes-0: 255 255 255;
--color-yes-50: 249 249 249;
--color-yes-100: 242 242 242;
--color-yes-200: 217 217 217;
--color-yes-300: 191 191 191;
--color-yes-400: 166 166 166;
--color-yes-500: 140 140 140;
--color-yes-600: 115 115 115;
--color-yes-700: 89 89 89;
--color-yes-800: 64 64 64;
--color-yes-900: 34 34 34;
--color-yes-950: 17 17 17;
--color-yes-1000: 0 0 0;
:root {
/* Text / Ink Grey Scale */
--color-ink-0: 255 255 255; /* white */
--color-ink-50: 245 245 245;
--color-ink-100: 230 230 230;
--color-ink-200: 200 200 200;
--color-ink-300: 170 170 170;
--color-ink-400: 140 140 140;
--color-ink-500: 110 110 110;
--color-ink-600: 85 85 85;
--color-ink-700: 60 60 60;
--color-ink-800: 40 40 40;
--color-ink-900: 25 25 25;
--color-ink-950: 12 12 12;
--color-ink-1000: 0 0 0; /* black */
--color-no-0: 255 255 255;
--color-no-50: 249 249 249;
--color-no-100: 242 242 242;
--color-no-200: 217 217 217;
--color-no-300: 191 191 191;
--color-no-400: 166 166 166;
--color-no-500: 140 140 140;
--color-no-600: 115 115 115;
--color-no-700: 89 89 89;
--color-no-800: 64 64 64;
--color-no-900: 34 34 34;
--color-no-950: 17 17 17;
--color-no-1000: 0 0 0;
/* Background / Canvas Grey Scale */
--color-canvas-0: 255 255 255; /* white */
--color-canvas-25: 245 245 245;
--color-canvas-50: 245 245 245;
--color-canvas-100: 245 245 245;
--color-canvas-200: 210 210 210;
--color-canvas-300: 190 190 190;
--color-canvas-400: 160 160 160;
--color-canvas-500: 130 130 130;
--color-canvas-600: 100 100 100;
--color-canvas-700: 75 75 75;
--color-canvas-800: 50 50 50;
--color-canvas-900: 30 30 30;
--color-canvas-950: 15 15 15;
--color-canvas-1000: 0 0 0; /* black */
}
.dark {
color-scheme: dark;
--color-primary-950: 23 37 84; /* very dark navy */
--color-primary-900: 30 58 138;
--color-primary-800: 30 64 175;
--color-primary-700: 29 78 216;
--color-primary-600: 37 99 235;
--color-primary-500: 59 130 246; /* standard blue */
--color-primary-400: 96 165 250;
--color-primary-300: 147 197 253;
--color-primary-200: 191 219 254;
--color-primary-100: 219 234 254;
--color-primary-50: 239 246 255; /* very light blue */
--color-ink-1000: 255 255 255; /* white */
--color-ink-950: 250 250 250; /* #FAFAFA */
--color-ink-900: 242 242 242; /* #F2F2F2 */
--color-ink-800: 217 217 217; /* #D9D9D9 */
--color-ink-700: 191 191 191; /* #BFBFBF */
--color-ink-600: 166 166 166; /* #A6A6A6 */
--color-ink-500: 140 140 140; /* #8C8C8C */
--color-ink-400: 115 115 115; /* #737373 */
--color-ink-300: 89 89 89; /* #595959 */
--color-ink-200: 64 64 64; /* #404040 */
--color-ink-100: 34 34 34; /* #222222 */
--color-ink-50: 17 17 17; /* #111111 */
--color-ink-0: 0 0 0; /* black */
--color-yes-0: 255 255 255;
--color-yes-50: 249 249 249;
--color-yes-100: 242 242 242;
--color-yes-200: 217 217 217;
--color-yes-300: 191 191 191;
--color-yes-400: 166 166 166;
--color-yes-500: 140 140 140;
--color-yes-600: 115 115 115;
--color-yes-700: 89 89 89;
--color-yes-800: 64 64 64;
--color-yes-900: 34 34 34;
--color-yes-950: 17 17 17;
--color-yes-1000: 0 0 0;
--color-canvas-0: 32 30 37;
--color-canvas-50: 22 20 25;
--color-canvas-100: 46 41 51;
--color-no-0: 255 255 255;
--color-no-50: 249 249 249;
--color-no-100: 242 242 242;
--color-no-200: 217 217 217;
--color-no-300: 191 191 191;
--color-no-400: 166 166 166;
--color-no-500: 140 140 140;
--color-no-600: 115 115 115;
--color-no-700: 89 89 89;
--color-no-800: 64 64 64;
--color-no-900: 34 34 34;
--color-no-950: 17 17 17;
--color-no-1000: 0 0 0;
--color-primary-950: 239 246 255; /* very light blue */
--color-primary-900: 219 234 254; /* light blue */
--color-primary-800: 191 219 254;
--color-primary-700: 147 197 253;
--color-primary-600: 96 165 250;
--color-primary-500: 59 130 246; /* standard blue */
--color-primary-400: 37 99 235;
--color-primary-300: 29 78 216;
--color-primary-200: 30 64 175;
--color-primary-100: 30 58 138;
--color-primary-50: 23 37 84; /* very dark navy */
}
.dark {
color-scheme: dark;
--color-ink-1000: 255 255 255; /* white */
--color-ink-950: 250 250 250; /* #FAFAFA */
--color-ink-900: 242 242 242; /* #F2F2F2 */
--color-ink-800: 217 217 217; /* #D9D9D9 */
--color-ink-700: 191 191 191; /* #BFBFBF */
--color-ink-600: 166 166 166; /* #A6A6A6 */
--color-ink-500: 140 140 140; /* #8C8C8C */
--color-ink-400: 115 115 115; /* #737373 */
--color-ink-300: 89 89 89; /* #595959 */
--color-ink-200: 64 64 64; /* #404040 */
--color-ink-100: 34 34 34; /* #222222 */
--color-ink-50: 17 17 17; /* #111111 */
--color-ink-0: 0 0 0; /* black */
--color-canvas-25: 0 0 0;
--color-canvas-0: 20 20 20;
--color-canvas-50: 20 20 20;
--color-canvas-100: 40 40 40;
--color-primary-950: 239 246 255; /* very light blue */
--color-primary-900: 219 234 254; /* light blue */
--color-primary-800: 191 219 254;
--color-primary-700: 147 197 253;
--color-primary-600: 96 165 250;
--color-primary-500: 59 130 246; /* standard blue */
--color-primary-400: 37 99 235;
--color-primary-300: 29 78 216;
--color-primary-200: 30 64 175;
--color-primary-100: 30 58 138;
--color-primary-50: 23 37 84; /* very dark navy */
--color-no-950: 255 255 255; /* white */
--color-no-900: 242 242 242;
--color-no-800: 217 217 217;
--color-no-700: 191 191 191;
--color-no-600: 166 166 166;
--color-no-500: 140 140 140;
--color-no-400: 115 115 115;
--color-no-300: 89 89 89;
--color-no-200: 64 64 64;
--color-no-100: 34 34 34;
--color-no-50: 17 17 17;
--color-no-0: 0 0 0; /* black */
--color-no-950: 255 255 255; /* white */
--color-no-900: 242 242 242;
--color-no-800: 217 217 217;
--color-no-700: 191 191 191;
--color-no-600: 166 166 166;
--color-no-500: 140 140 140;
--color-no-400: 115 115 115;
--color-no-300: 89 89 89;
--color-no-200: 64 64 64;
--color-no-100: 34 34 34;
--color-no-50: 17 17 17;
--color-no-0: 0 0 0; /* black */
--color-yes-950: 255 255 255; /* white */
--color-yes-900: 242 242 242;
--color-yes-800: 217 217 217;
--color-yes-700: 191 191 191;
--color-yes-600: 166 166 166;
--color-yes-500: 140 140 140;
--color-yes-400: 115 115 115;
--color-yes-300: 89 89 89;
--color-yes-200: 64 64 64;
--color-yes-100: 34 34 34;
--color-yes-50: 17 17 17;
--color-yes-0: 0 0 0; /* black */
--color-yes-950: 255 255 255; /* white */
--color-yes-900: 242 242 242;
--color-yes-800: 217 217 217;
--color-yes-700: 191 191 191;
--color-yes-600: 166 166 166;
--color-yes-500: 140 140 140;
--color-yes-400: 115 115 115;
--color-yes-300: 89 89 89;
--color-yes-200: 64 64 64;
--color-yes-100: 34 34 34;
--color-yes-50: 17 17 17;
--color-yes-0: 0 0 0; /* black */
}
}
}
@font-face {
font-family: 'emoji';
src: local('AppleColorEmoji') local('Segoe UI Emoji'),
local('Noto Color Emoji');
/* from official unicode range for emoji: https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AEmoji%3DYes%3A%5D%0D%0A&abb=on&esc=on&g=&i= */
/* but include zero width joiner and variant block selector, like 🏳️‍🌈 */
unicode-range: U+200D, U+FE0?, U+203C, U+2049, U+2122, U+2139, U+2194-2199,
font-family: 'emoji';
src: local('AppleColorEmoji'), local('Segoe UI Emoji'), local('Noto Color Emoji');
/* from official unicode range for emoji: https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AEmoji%3DYes%3A%5D%0D%0A&abb=on&esc=on&g=&i= */
/* but include zero width joiner and variant block selector, like 🏳️‍🌈 */
unicode-range: U+200D, U+FE0?, U+203C, U+2049, U+2122, U+2139, U+2194-2199,
U+21A9, U+21AA, U+231A, U+231B, U+2328, U+23CF, U+23E9-23F3, U+23F8-23FA,
U+24C2, U+25AA, U+25AB, U+25B6, U+25C0, U+25FB-25FE, U+2600-2604, U+260E,
U+2611, U+2614, U+2615, U+2618, U+261D, U+2620, U+2622, U+2623, U+2626,
@@ -176,57 +191,56 @@
}
@font-face {
font-family: 'icomoon';
src: url('../public/fonts/icomoon.eot?v49ui9#iefix')
format('embedded-opentype'),
font-family: 'icomoon';
src: url('../public/fonts/icomoon.eot?v49ui9#iefix') format('embedded-opentype'),
url('../public/fonts/icomoon.ttf?v49ui9') format('truetype'),
url('../public/fonts/icomoon.woff?v49ui9') format('woff'),
url('../public/fonts/icomoon.svg?v49ui9#icomoon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
unicode-range: U+1E40;
font-weight: normal;
font-style: normal;
font-display: block;
unicode-range: U+1E40;
}
[class^='icon-'],
[class*=' icon-'] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-mana_3:before {
content: '\1e40';
content: '\1e40';
}
/* For Webkit-inkd browsers (Chrome, Safari and Opera) */
.scrollbar-hide::-webkit-scrollbar {
display: none;
display: none;
}
/* For IE, Edge and Firefox */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
text {
font-family: icomoon, var(--font-main), emoji, sans-serif;
font-family: icomoon, var(--font-main), emoji, sans-serif;
}
/* 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 */
/*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;
@@ -235,27 +249,53 @@ h1, h2, h3, h4, h5, h6 {
/* Size scaling */
h1 {
font-size: 2rem; /* ~32px */
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 */
font-size: 1rem; /* ~16px */
}
h6 {
font-size: 0.875rem; /* ~14px */
color: #374151; /* Slightly lighter for subheadings */
color: #374151; /* Slightly lighter for subheadings */
}
ul {
list-style: disc;
padding-left: 1.25rem;
margin-top: 0.5rem;
}
.customlink a {
color: rgb(var(--color-primary-500));
text-decoration: none;
}
.dark .customlink a {
color: rgb(var(--color-primary-700));
}
.customlink a:hover {
text-decoration: underline;
}
.sidebar-nav {
font-family: "Inter", sans-serif; /* clean and legible */
}
.body-bg {
background-color: rgb(var(--color-canvas-0));
}

View File

@@ -310,6 +310,7 @@ module.exports = {
},
canvas: {
0: 'rgb(var(--color-canvas-0) / <alpha-value>)',
25: 'rgb(var(--color-canvas-25) / <alpha-value>)',
50: 'rgb(var(--color-canvas-50) / <alpha-value>)',
100: 'rgb(var(--color-canvas-100) / <alpha-value>)',
},
@@ -328,17 +329,17 @@ module.exports = {
950: 'rgb(var(--color-primary-950) / <alpha-value>)',
},
gray: {
50: 'hsl(240, 40%, 98%)',
100: 'hsl(235, 46%, 95%)',
200: 'hsl(236, 33%, 90%)',
300: 'hsl(238, 26%, 81%)',
400: 'hsl(238, 19%, 68%)',
500: 'hsl(240, 12%, 52%)',
600: 'hsl(240, 16%, 40%)',
700: 'hsl(240, 20%, 30%)',
800: 'hsl(240, 30%, 22%)',
900: 'hsl(242, 45%, 15%)',
950: 'hsl(243, 69%, 10%)',
50: 'hsl(0, 0%, 95%)',
100: 'hsl(0, 0%, 90%)',
200: 'hsl(0, 0%, 80%)',
300: 'hsl(0, 0%, 70%)',
400: 'hsl(0, 0%, 60%)',
500: 'hsl(0, 0%, 50%)',
600: 'hsl(0, 0%, 40%)',
700: 'hsl(0, 0%, 30%)',
800: 'hsl(0, 0%, 20%)',
900: 'hsl(0,0%,10%)',
950: 'hsl(0,0%,5%)',
},
warning: '#F0D630',
error: '#E70D3D',

View File

File diff suppressed because it is too large Load Diff

108
yarn.lock
View File

@@ -28,6 +28,15 @@
"@babel/highlight" "^7.24.2"
picocolors "^1.0.0"
"@babel/code-frame@^7.10.4":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be"
integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
dependencies:
"@babel/helper-validator-identifier" "^7.27.1"
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.4":
version "7.24.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a"
@@ -230,6 +239,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62"
integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==
"@babel/helper-validator-identifier@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
"@babel/helper-validator-option@^7.23.5":
version "7.23.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307"
@@ -3029,6 +3043,20 @@
lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10"
"@testing-library/dom@^10.0.0":
version "10.4.1"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.1.tgz#d444f8a889e9a46e9a3b4f3b88e0fcb3efb6cf95"
integrity sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^5.0.1"
aria-query "5.3.0"
dom-accessibility-api "^0.5.9"
lz-string "^1.5.0"
picocolors "1.1.1"
pretty-format "^27.0.2"
"@testing-library/jest-dom@^6.6.4":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz#697db9424f0d21d8216f1958fa0b1b69b5f43923"
@@ -3288,6 +3316,11 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
"@types/aria-query@^5.0.1":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==
"@types/babel__core@^7.1.14":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -3786,11 +3819,6 @@
dependencies:
"@types/react" "*"
"@types/react-dom@19.0.4":
version "19.0.4"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.4.tgz#bedba97f9346bd4c0fe5d39e689713804ec9ac89"
integrity sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==
"@types/react@*", "@types/react@18.3.5":
version "18.3.5"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.5.tgz#5f524c2ad2089c0ff372bbdabc77ca2c4dbadf8f"
@@ -3799,13 +3827,6 @@
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/react@19.0.10":
version "19.0.10"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.10.tgz#d0c66dafd862474190fe95ce11a68de69ed2b0eb"
integrity sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==
dependencies:
csstype "^3.0.2"
"@types/request@^2.48.8":
version "2.48.12"
resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30"
@@ -4193,18 +4214,18 @@ aria-hidden@^1.1.3:
dependencies:
tslib "^2.0.0"
aria-query@^5.0.0:
version "5.3.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
aria-query@^5.3.0:
aria-query@5.3.0, aria-query@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e"
integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==
dependencies:
dequal "^2.0.3"
aria-query@^5.0.0:
version "5.3.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
array-buffer-byte-length@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f"
@@ -5417,6 +5438,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-accessibility-api@^0.5.9:
version "0.5.16"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
dom-accessibility-api@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8"
@@ -8149,6 +8175,11 @@ lru-memoizer@^2.2.0:
lodash.clonedeep "^4.5.0"
lru-cache "6.0.0"
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
make-dir@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
@@ -9265,16 +9296,16 @@ pgpass@1.x:
dependencies:
split2 "^4.1.0"
picocolors@1.1.1, picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
@@ -9471,6 +9502,15 @@ prettier@3.4.2:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f"
integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==
pretty-format@^27.0.2:
version "27.5.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
dependencies:
ansi-regex "^5.0.1"
ansi-styles "^5.0.0"
react-is "^17.0.1"
pretty-format@^29.0.0, pretty-format@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
@@ -9860,13 +9900,6 @@ react-dom@18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-dom@19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
dependencies:
scheduler "^0.25.0"
react-email@3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/react-email/-/react-email-3.0.7.tgz#8b52684f157c5d5e6200bc201590827aaf9dc9ec"
@@ -9923,6 +9956,11 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^18.0.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
@@ -9979,11 +10017,6 @@ react@18.2.0:
dependencies:
loose-envify "^1.1.0"
react@19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd"
integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==
read-cache@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
@@ -10320,11 +10353,6 @@ scheduler@^0.23.0:
dependencies:
loose-envify "^1.1.0"
scheduler@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015"
integrity sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==
selderee@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/selderee/-/selderee-0.11.0.tgz#6af0c7983e073ad3e35787ffe20cefd9daf0ec8a"