Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion truecover-app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ COPY --from=build /app/build /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Copy entrypoint script for runtime config injection
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
# Use entrypoint to generate env-config.js at container startup
ENTRYPOINT ["/docker-entrypoint.sh"]
19 changes: 19 additions & 0 deletions truecover-app/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh
# Docker entrypoint script for runtime environment configuration
# Generates env-config.js with environment variables at container startup

# Create env-config.js with runtime environment variables
cat > /usr/share/nginx/html/env-config.js << EOF
// Runtime environment configuration - generated at container startup
window.__ENV__ = {
VITE_API_URL: "${VITE_API_URL:-}",
VITE_MARTIN_URL: "${VITE_MARTIN_URL:-}",
VITE_MAPBOX_TOKEN: "${VITE_MAPBOX_TOKEN:-}",
VITE_CLERK_PUBLISHABLE_KEY: "${VITE_CLERK_PUBLISHABLE_KEY:-}"
};
EOF

echo "Generated /usr/share/nginx/html/env-config.js with runtime config"

# Start nginx
exec nginx -g 'daemon off;'
2 changes: 2 additions & 0 deletions truecover-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>True Cover</title>
<!-- Runtime environment config - generated by docker-entrypoint.sh -->
<script src="/env-config.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/components/AddVisitModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { useRounds } from '../hooks/useRounds';
import { FileData } from '../types';
import axios from 'axios';
import { useAuth } from '@clerk/clerk-react';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface AddVisitModalProps {
isOpen: boolean;
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/components/AuthenticatedApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import {
TacticalBadge,
} from '../tactical-ui';
import { useAuth } from '@clerk/clerk-react';
import { env } from '../config/env';

type AppView = 'home' | 'adaptive-sampling' | 'coverage-prediction';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000';
const API_URL = env.VITE_API_URL;

function AuthenticatedApp() {
const { getToken } = useAuth();
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/components/CreateRoundModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import axios from 'axios';
import { useAuth } from '@clerk/clerk-react';
import { useIndicators } from '../hooks/useIndicators';
import { usePixelMetadataStats } from '../hooks/usePixels';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface CreateRoundModalProps {
isOpen: boolean;
Expand Down
5 changes: 3 additions & 2 deletions truecover-app/src/components/MapView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useAuth } from '@clerk/clerk-react';
import { adminBoundariesApi, pixelsApi } from '../services/api';
import { useGeneratePixels } from '../hooks/usePixels';
import { tacticalToast } from '../tactical-ui';
import { env } from '../config/env';

interface CampaignArea {
id: string;
Expand Down Expand Up @@ -85,7 +86,7 @@ const getCentroid = (geometry: any): [number, number] => {
return [sum[0] / coords.length, sum[1] / coords.length];
};

const MARTIN_URL = import.meta.env.VITE_MARTIN_URL || 'http://localhost:3052';
const MARTIN_URL = env.VITE_MARTIN_URL;

const MapView: React.FC<MapViewProps> = ({ data, selectedData, locations, mode = 'sampling', highlightRounds = [], showSampled = true, onToggleSampled, interpolationMode = 'none', selectedMetadataField = '', metadataVisualizationMode = 'fill', showPixels = false, onTogglePixels, pixelsBounds, onBoundsChange, campaignId, indicatorId, pixelVersion, pixelCount = 0, onGeneratePixels, histogramBrushRanges = null, histogramDataType = 'locations', sampledItemsCount = 0, planningMode = false, campaignAreas = [], showCampaignAreas = true, onToggleCampaignAreas, onAddAdminBoundaryToCampaign, selectedAreaBounds, className }) => {
const [popupInfo, setPopupInfo] = useState<any>(null);
Expand Down Expand Up @@ -116,7 +117,7 @@ const MapView: React.FC<MapViewProps> = ({ data, selectedData, locations, mode =
const [drawnFeature, setDrawnFeature] = useState<any>(null);
const drawControlRef = useRef<MapboxDraw | null>(null);
const geocoderInputRef = useRef<HTMLDivElement>(null);
const mapboxToken = import.meta.env.VITE_MAPBOX_TOKEN;
const mapboxToken = env.VITE_MAPBOX_TOKEN;

const { getToken } = useAuth();
const generatePixelsMutation = useGeneratePixels();
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/components/RoundsManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { StratifiedClusterSamplingWizard } from './StratifiedClusterSamplingWiza
import axios from 'axios';
import { useAuth } from '@clerk/clerk-react';
import { useIndicators } from '../hooks/useIndicators';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface Round {
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import {
import { DraggableAreaCard } from './DraggableAreaCard';
import { CategoryColumn } from './CategoryColumn';
import { useDivisions, useDistricts, useAdminBoundaryChildren } from '../hooks/useAdminBoundaries';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface WorkflowProgress {
status: string;
Expand Down
41 changes: 41 additions & 0 deletions truecover-app/src/config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Runtime environment configuration with fallback to build-time values
// This allows environment variables to be injected at container startup
// via env-config.js, while still working in development with Vite's import.meta.env

declare global {
interface Window {
__ENV__?: {
VITE_API_URL?: string;
VITE_MARTIN_URL?: string;
VITE_MAPBOX_TOKEN?: string;
VITE_CLERK_PUBLISHABLE_KEY?: string;
};
}
}

// Helper to get env value with runtime override support
const getEnv = (key: string, fallback: string = ''): string => {
// First check runtime config (injected by docker-entrypoint.sh)
const runtimeValue = window.__ENV__?.[key as keyof typeof window.__ENV__];
if (runtimeValue) {
return runtimeValue;
}

// Fall back to build-time Vite env
const buildValue = import.meta.env[key];
if (buildValue) {
return buildValue;
}

// Return fallback
return fallback;
};

export const env = {
VITE_API_URL: getEnv('VITE_API_URL', 'http://localhost:5001'),
VITE_MARTIN_URL: getEnv('VITE_MARTIN_URL', 'http://localhost:3052'),
VITE_MAPBOX_TOKEN: getEnv('VITE_MAPBOX_TOKEN', ''),
VITE_CLERK_PUBLISHABLE_KEY: getEnv('VITE_CLERK_PUBLISHABLE_KEY', ''),
};

export default env;
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useAdaptiveSampling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { useState } from 'react';
import { useAuth } from '@clerk/clerk-react';
import axios from 'axios';
import { FileData, SamplingRequest } from '../types';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

export const useAdaptiveSampling = () => {
const { getToken } = useAuth();
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useCampaignAreas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import { useQuery } from '@tanstack/react-query';
import { useAuth } from '@clerk/clerk-react';
import axios from 'axios';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

export interface CampaignArea {
id: string;
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useCoverage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import axios from 'axios';
import { useAuth } from '@clerk/clerk-react';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface PredictCoverageParams {
campaign_id: string;
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useCoverageData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { useAuth } from '@clerk/clerk-react';
import axios from 'axios';
import { CoverageRecord, CoveragePixelRecord } from './useCoverage';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface CoverageDataParams {
campaign_id: string;
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useCoveragePrediction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { useAuth } from '@clerk/clerk-react';
import axios from 'axios';
import { FileData } from '../types';
import { mergeSampleFrameAndSurvey } from '../utils/dataMerger';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

export const useCoveragePrediction = () => {
const { getToken } = useAuth();
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useLocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { useQuery, useInfiniteQuery, useMutation, useQueryClient } from '@tansta
import { useAuth } from '@clerk/clerk-react';
import { locationsApi } from '../services/api';
import axios from 'axios';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

/**
* Hook to fetch locations for a specific area
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useRounds.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { useAuth } from '@clerk/clerk-react';
import axios from 'axios';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface Round {
id: string;
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useUserSync.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useEffect } from 'react';
import { useAuth } from '@clerk/clerk-react';
import axios from 'axios';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

/**
* Hook to automatically sync Clerk user with backend database on sign-in
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useVisitIndicators.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useAuth } from '@clerk/clerk-react';
import axios from 'axios';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface VisitIndicator {
id: string;
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/hooks/useVisits.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useAuth } from '@clerk/clerk-react';
import axios from 'axios';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

interface Visit {
id: string;
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ClerkProvider } from '@clerk/clerk-react';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { env } from './config/env';

// Create a client
const queryClient = new QueryClient({
Expand All @@ -19,7 +20,7 @@ const queryClient = new QueryClient({
},
});

const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
const clerkPubKey = env.VITE_CLERK_PUBLISHABLE_KEY;

if (!clerkPubKey) {
throw new Error('Missing Clerk Publishable Key');
Expand Down
3 changes: 2 additions & 1 deletion truecover-app/src/services/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import axios from 'axios';
import { Organization, OrganizationMember, Project, Campaign } from '../types';
import { env } from '../config/env';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001';
const API_URL = env.VITE_API_URL;

// Organization API calls
export const organizationsApi = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export const TacticalMultiSelect: React.FC<TacticalMultiSelectProps> = ({

const toggleOption = (optionValue: number | string) => {
let newValue: (number | string)[];
const nonAllOptions = options.filter(opt => opt.value !== 'all').map(opt => opt.value);

console.log('[TacticalMultiSelect] toggleOption called with:', optionValue);
console.log('[TacticalMultiSelect] current pendingValue:', pendingValue);
Expand Down