mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-04-03 22:44:35 -04:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09736cd49b | ||
|
|
f16bef97dc | ||
|
|
92d4222f96 | ||
|
|
008110b015 | ||
|
|
29ace2d2e5 | ||
|
|
07f927d738 | ||
|
|
993117ba72 | ||
|
|
c8801a0235 | ||
|
|
93cd105871 | ||
|
|
4f98d99dd9 | ||
|
|
b46d39d9b7 | ||
|
|
5ea095662b | ||
|
|
2400d50247 | ||
|
|
8ffd69ff15 | ||
|
|
0b721ec7b9 | ||
|
|
2019c835a0 | ||
|
|
ff23a8c1bc | ||
|
|
df775e9aa3 |
102
.coderabbit.yaml
Normal file
102
.coderabbit.yaml
Normal file
@@ -0,0 +1,102 @@
|
||||
# Enables IDE autocompletion for this config file
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
|
||||
# Language for CodeRabbit's review comments
|
||||
language: en
|
||||
|
||||
# Enable experimental features (currently not using any specific early_access features)
|
||||
early_access: true
|
||||
|
||||
chat:
|
||||
# CodeRabbit will automatically respond to @coderabbitai mentions in PR comments
|
||||
auto_reply: true
|
||||
|
||||
reviews:
|
||||
auto_review:
|
||||
# Automatically trigger reviews when PRs are opened or updated
|
||||
enabled: true
|
||||
# Skip auto-review if PR title contains these keywords
|
||||
ignore_title_keywords:
|
||||
- "WIP"
|
||||
# Don't auto-review draft PRs
|
||||
drafts: false
|
||||
# Only auto-review PRs targeting these branches
|
||||
base_branches:
|
||||
- main
|
||||
- develop
|
||||
|
||||
# Include a high-level summary at the start of each review
|
||||
high_level_summary: true
|
||||
|
||||
# Generate sequence diagrams for complex code flows
|
||||
sequence_diagrams: true
|
||||
|
||||
# Don't include poems in reviews (fun feature, but keeping it professional)
|
||||
poem: false
|
||||
|
||||
# Show review completion status
|
||||
review_status: true
|
||||
|
||||
# Keep the walkthrough section expanded by default
|
||||
collapse_walkthrough: false
|
||||
|
||||
# Include summary of all changed files
|
||||
changed_files_summary: true
|
||||
|
||||
# Don't automatically request changes on the PR (just leave comments)
|
||||
request_changes_workflow: false
|
||||
|
||||
# Pre-merge checks to enforce before merging PRs
|
||||
pre_merge_checks:
|
||||
description:
|
||||
# Validate that PR has a proper description
|
||||
mode: warning # Options: off, warning, error
|
||||
docstrings:
|
||||
# Disable docstring coverage checks (let's assume we don't need them)
|
||||
mode: off
|
||||
|
||||
# Exclude these paths from reviews (build artifacts and dependencies)
|
||||
path_filters:
|
||||
- "!**/node_modules/**" # npm dependencies
|
||||
- "!**/android/**" # Native Android build files
|
||||
- "!**/ios/**" # Native iOS build files
|
||||
- "!**/.expo/**" # Expo build cache
|
||||
- "!**/.expo-shared/**" # Expo shared config
|
||||
- "!**/dist/**" # Build output
|
||||
|
||||
# Custom review instructions for specific file patterns
|
||||
path_instructions:
|
||||
# TypeScript/JavaScript files - main app code
|
||||
- path: "**/*.{ts,tsx,js,jsx}"
|
||||
instructions: |
|
||||
General practices:
|
||||
- Summarize the changes clearly.
|
||||
- Format the summary with bullet points.
|
||||
- Highlight any potential breaking changes for users.
|
||||
- We use early returns to avoid deep nesting.
|
||||
- Ensure all public functions have docstrings.
|
||||
- Flag any hardcoded strings; they should be in the constants file.
|
||||
- Check for edge cases like null values or empty arrays.
|
||||
- Suggest performance optimizations where appropriate.
|
||||
|
||||
Mobile best practices:
|
||||
- Proper use of hooks (useRouter, useFonts, useAssets)
|
||||
- Accessibility: touch targets min 44x44, screen reader support
|
||||
- Safe area handling and platform-specific code (iOS vs Android)
|
||||
- Memory leaks in useEffect and event listeners
|
||||
|
||||
Performance:
|
||||
- Use FlatList/SectionList for lists (never ScrollView with .map)
|
||||
- React.memo, useMemo, useCallback where appropriate
|
||||
|
||||
TypeScript:
|
||||
- Avoid 'any', use explicit types
|
||||
- Prefer 'import type' for type imports
|
||||
|
||||
Security:
|
||||
- No exposed API keys or sensitive data
|
||||
- Use expo-secure-store for sensitive storage
|
||||
- Validate deep linking configurations
|
||||
|
||||
Internationalization:
|
||||
- User-visible strings should be externalized to resource files (useT())
|
||||
5
.coderabbitignore
Normal file
5
.coderabbitignore
Normal file
@@ -0,0 +1,5 @@
|
||||
**/*.md
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
dist/**
|
||||
test/mocks/**
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.compassconnections.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 79
|
||||
versionName "1.16.0"
|
||||
versionCode 84
|
||||
versionName "1.17.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -238,7 +238,16 @@ public class MainActivity extends BridgeActivity implements ModifiedMainActivity
|
||||
checkForUpdates();
|
||||
|
||||
Uri data = getIntent().getData();
|
||||
if (data != null) pendingDeepLink = data.toString();
|
||||
if (data != null) {
|
||||
pendingDeepLink = data.toString();
|
||||
} else {
|
||||
// Check for notification endpoint when app is opened from cold start via notification click
|
||||
String endpoint = getIntent().getStringExtra("endpoint");
|
||||
if (endpoint != null) {
|
||||
Log.i("CompassApp", "onCreate found endpoint from notification: " + endpoint);
|
||||
pendingDeepLink = endpoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeepLink(String url) {
|
||||
|
||||
@@ -55,7 +55,11 @@ export const LOCAL_BACKEND_DOMAIN = `${IS_WEBVIEW_DEV_PHONE ? '192.168.1.3' : IS
|
||||
|
||||
export const DOMAIN = IS_LOCAL ? LOCAL_WEB_DOMAIN : ENV_CONFIG.domain
|
||||
export const DEPLOYED_WEB_URL = `https://www.${ENV_CONFIG.domain}`
|
||||
export const WEB_URL = IS_LOCAL ? `http://${LOCAL_WEB_DOMAIN}` : `https://www.${DOMAIN}`
|
||||
|
||||
// Careful: buildOgUrl uses WEB_URL and works only on https://www.compassmeet.com (not https://compassmeet.com)
|
||||
// x-vercel-error: INVALID_IMAGE_OPTIMIZE_REQUEST. Could work if needed though with some Vercel tweak
|
||||
export const WEB_URL = IS_LOCAL ? `http://${LOCAL_WEB_DOMAIN}` : `https://${DOMAIN}`
|
||||
|
||||
export const BACKEND_DOMAIN = IS_LOCAL ? LOCAL_BACKEND_DOMAIN : ENV_CONFIG.backendDomain
|
||||
export const FIREBASE_CONFIG = ENV_CONFIG.firebaseConfig
|
||||
export const PROJECT_ID = ENV_CONFIG.firebaseConfig.projectId
|
||||
|
||||
@@ -66,7 +66,9 @@ export async function run<T>(
|
||||
export async function run<T>(q: PromiseLike<PostgrestSingleResponse<T> | PostgrestResponse<T>>) {
|
||||
const {data, count, error} = await q
|
||||
if (error != null) {
|
||||
throw error
|
||||
const err = new Error(error.message)
|
||||
Object.assign(err, error) // copies code, details, hint onto the Error
|
||||
throw err
|
||||
} else {
|
||||
return {data, count}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export const ViewProfileCardButton = (props: {
|
||||
src={src}
|
||||
width={width}
|
||||
height={height}
|
||||
alt={t('profile_card.loading', 'Loading your card, it may take a few seconds...')}
|
||||
alt={t('profile_card.loading', `${user.username}'s profile card`)}
|
||||
className={'rounded-2xl'}
|
||||
/>
|
||||
<ShareProfileButtons
|
||||
|
||||
@@ -3,13 +3,13 @@ export function SkipLink() {
|
||||
<>
|
||||
<a
|
||||
href="#main-content"
|
||||
className="absolute -top-10 left-4 z-50 bg-primary-500 px-4 py-2 text-white transition-all focus:top-4"
|
||||
className="absolute -top-10 left-4 z-50 bg-canvas-100 px-4 py-2 transition-all focus:top-4"
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
<a
|
||||
href="#main-navigation"
|
||||
className="absolute -top-10 left-4 z-50 bg-primary-500 px-4 py-2 text-white transition-all focus:top-4"
|
||||
className="absolute -top-10 left-4 z-50 bg-canvas-100 px-4 py-2 transition-all focus:top-4"
|
||||
>
|
||||
Skip to navigation
|
||||
</a>
|
||||
|
||||
@@ -6,13 +6,15 @@ import {
|
||||
} from 'common/profiles/profile'
|
||||
import {Row} from 'common/supabase/utils'
|
||||
import {User} from 'common/user'
|
||||
import {useEffect} from 'react'
|
||||
import {createContext, ReactNode, useContext, useEffect} from 'react'
|
||||
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
|
||||
import {usePersistentLocalState} from 'web/hooks/use-persistent-local-state'
|
||||
import {useUser} from 'web/hooks/use-user'
|
||||
import {db} from 'web/lib/supabase/db'
|
||||
|
||||
export const useProfile = () => {
|
||||
type OwnProfile = (Row<'profiles'> & {user: User}) | null | undefined
|
||||
|
||||
const useOwnProfile = (): OwnProfile => {
|
||||
const user = useUser()
|
||||
const [profile, setProfile] = usePersistentLocalState<Row<'profiles'> | undefined | null>(
|
||||
undefined,
|
||||
@@ -20,13 +22,11 @@ export const useProfile = () => {
|
||||
)
|
||||
|
||||
const refreshProfile = () => {
|
||||
if (user) {
|
||||
// logger.debug('Refreshing profile in useProfile for', user?.username, profile);
|
||||
getProfileRowWithFrontendSupabase(user.id, db).then((profile) => {
|
||||
if (!profile) setProfile(null)
|
||||
else setProfile(profile)
|
||||
})
|
||||
}
|
||||
if (!user?.id) return
|
||||
debug('Refreshing own profile for', user.username)
|
||||
getProfileRowWithFrontendSupabase(user.id, db).then((p) => {
|
||||
setProfile(p ?? null)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -36,6 +36,21 @@ export const useProfile = () => {
|
||||
return user && profile ? {...profile, user} : profile === null ? null : undefined
|
||||
}
|
||||
|
||||
const MISSING = Symbol('missing')
|
||||
|
||||
const ProfileContext = createContext<OwnProfile | typeof MISSING>(MISSING)
|
||||
|
||||
export const useProfile = () => {
|
||||
const ctx = useContext(ProfileContext)
|
||||
if (ctx === MISSING) throw new Error('useProfile must be used within a ProfileProvider')
|
||||
return ctx as OwnProfile
|
||||
}
|
||||
|
||||
export const ProfileProvider = ({children}: {children: ReactNode}) => {
|
||||
const profile = useOwnProfile()
|
||||
return <ProfileContext.Provider value={profile}>{children}</ProfileContext.Provider>
|
||||
}
|
||||
|
||||
export const useProfileByUser = (user: User | undefined) => {
|
||||
const userId = user?.id
|
||||
const [profile, setProfile] = usePersistentInMemoryState<Profile | undefined | null>(
|
||||
@@ -1,7 +1,7 @@
|
||||
import {PushNotifications} from '@capacitor/push-notifications'
|
||||
import {debug} from 'common/logger'
|
||||
import {useRouter} from 'next/router'
|
||||
import {useEffect} from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import {useUser} from 'web/hooks/use-user'
|
||||
import {api} from 'web/lib/api'
|
||||
import {isAndroidApp} from 'web/lib/util/webview'
|
||||
@@ -9,7 +9,6 @@ import {isAndroidApp} from 'web/lib/util/webview'
|
||||
export default function AndroidPush() {
|
||||
const user = useUser() // authenticated user
|
||||
const isAndroid = isAndroidApp()
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
if (!user?.id || !isAndroid) return
|
||||
debug('AndroidPush', user)
|
||||
@@ -36,12 +35,14 @@ export default function AndroidPush() {
|
||||
})
|
||||
|
||||
PushNotifications.addListener('pushNotificationReceived', (notif) => {
|
||||
debug('Push received', notif)
|
||||
const url = notif?.data?.url
|
||||
if (url) {
|
||||
router.push(url)
|
||||
window.location.href = url
|
||||
}
|
||||
console.debug('Push received', notif, window.location.pathname)
|
||||
const endpoint = notif?.data?.endpoint as string
|
||||
if (!endpoint) return
|
||||
if (!endpoint.startsWith('/messages/')) return
|
||||
if (endpoint === window.location.pathname) return
|
||||
const author = notif?.title
|
||||
const message = notif?.body
|
||||
toast.success(`${author}: "${message}"`)
|
||||
})
|
||||
}, [user?.id, isAndroid])
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ const nextConfig: NextConfig = {
|
||||
remotePatterns: [
|
||||
{hostname: 'martinbraquet.com'},
|
||||
{hostname: 'compassmeet.com'},
|
||||
{hostname: 'www.compassmeet.com'},
|
||||
{hostname: 'lh3.googleusercontent.com'},
|
||||
{hostname: 'i.imgur.com'},
|
||||
{hostname: 'firebasestorage.googleapis.com'},
|
||||
|
||||
@@ -5,10 +5,12 @@ import {App} from '@capacitor/app'
|
||||
import {Capacitor} from '@capacitor/core'
|
||||
import {Keyboard} from '@capacitor/keyboard'
|
||||
import {StatusBar} from '@capacitor/status-bar'
|
||||
import * as Sentry from '@sentry/node'
|
||||
import clsx from 'clsx'
|
||||
import {DEPLOYED_WEB_URL} from 'common/envs/constants'
|
||||
import {IS_VERCEL, PNG_FAVICON} from 'common/hosting/constants'
|
||||
import {debug} from 'common/logger'
|
||||
import {isUrl} from 'common/parsing'
|
||||
import type {AppProps} from 'next/app'
|
||||
import {Major_Mono_Display} from 'next/font/google'
|
||||
import Head from 'next/head'
|
||||
@@ -25,6 +27,7 @@ import {useFontPreferenceManager} from 'web/hooks/use-font-preference'
|
||||
import {useHasLoaded} from 'web/hooks/use-has-loaded'
|
||||
import {HiddenProfilesProvider} from 'web/hooks/use-hidden-profiles'
|
||||
import {PinnedQuestionIdsProvider} from 'web/hooks/use-pinned-question-ids'
|
||||
import {ProfileProvider} from 'web/hooks/use-profile'
|
||||
import {updateStatusBar} from 'web/hooks/use-theme'
|
||||
import {updateBackendLocale} from 'web/lib/api'
|
||||
import {DAYJS_LOCALE_IMPORTS, registerDatePickerLocale} from 'web/lib/dayjs'
|
||||
@@ -162,10 +165,23 @@ function MyApp(props: AppProps<PageProps>) {
|
||||
|
||||
const link = window.AndroidBridge?.getPendingDeepLink?.()
|
||||
if (link) {
|
||||
handleAppLink({url: link, endpoint: new URL(link).pathname})
|
||||
handleAppLink({endpoint: isUrl(link) ? new URL(link).pathname : link})
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAppInfo = async () => {
|
||||
if (!Capacitor.isNativePlatform()) return
|
||||
const appInfo = await App.getInfo().catch((e) => debug('Could not load Android app info:', e))
|
||||
const appVersion = appInfo?.version
|
||||
if (appVersion) {
|
||||
Sentry.setTag('androidApp.version', appVersion)
|
||||
Sentry.setTag('androidApp.buildNumber', appInfo?.build)
|
||||
}
|
||||
}
|
||||
fetchAppInfo()
|
||||
}, [])
|
||||
|
||||
const title = 'Compass'
|
||||
const description = 'The platform for intentional connections'
|
||||
|
||||
@@ -208,15 +224,17 @@ function MyApp(props: AppProps<PageProps>) {
|
||||
<I18nContext.Provider value={{locale, setLocale}}>
|
||||
<ErrorBoundary>
|
||||
<AuthProvider serverUser={pageProps.auth}>
|
||||
<ChoicesProvider>
|
||||
<PinnedQuestionIdsProvider>
|
||||
<HiddenProfilesProvider>
|
||||
<WebPush />
|
||||
<AndroidPush />
|
||||
<Component {...pageProps} />
|
||||
</HiddenProfilesProvider>
|
||||
</PinnedQuestionIdsProvider>
|
||||
</ChoicesProvider>
|
||||
<ProfileProvider>
|
||||
<ChoicesProvider>
|
||||
<PinnedQuestionIdsProvider>
|
||||
<HiddenProfilesProvider>
|
||||
<WebPush />
|
||||
<AndroidPush />
|
||||
<Component {...pageProps} />
|
||||
</HiddenProfilesProvider>
|
||||
</PinnedQuestionIdsProvider>
|
||||
</ChoicesProvider>
|
||||
</ProfileProvider>
|
||||
</AuthProvider>
|
||||
</ErrorBoundary>
|
||||
</I18nContext.Provider>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"package_name": "com.compassconnections.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"29:61:EB:C6:DE:D4:E3:50:FE:8B:56:D2:B5:F1:E7:1D:8D:6F:96:82:26:05:63:5B:44:BB:56:45:B1:D7:6A:EE",
|
||||
"4B:80:99:49:D0:30:6F:8F:3C:D0:E7:BB:F6:39:CA:7B:99:A3:17:20:96:F9:4E:1F:13:CC:81:90:54:65:51:3B",
|
||||
"9D:88:96:66:81:E9:C5:82:5E:96:6C:B7:C4:A0:0C:AA:26:1B:0A:04:F8:7D:9D:F2:4D:C4:3E:02:99:A3:42:DF"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user