Compare commits

...

2 Commits

Author SHA1 Message Date
1570185792 closes #12 2025-12-11 15:28:33 -06:00
c48fc96b33 closes #8 2025-12-11 13:16:54 -06:00
8 changed files with 120 additions and 43 deletions

View File

@@ -1,7 +1,8 @@
import axios from 'axios';
import Cookies from 'js-cookie';
import { features } from './config/features';
const baseURL = import.meta.env.VITE_API_URL;
const baseURL = features.apiUrl;
console.log(baseURL);
export const axiosRealEstateApi = axios.create({

View File

@@ -395,11 +395,17 @@ const ChatPane = ({ showChat, isMinimized, toggleMinimize, closeChat }: ChatPane
);
};
const FloatingChatButton = (): ReactElement => {
import { features } from 'config/features';
const FloatingChatButton = (): ReactElement | null => {
const [showChat, setShowChat] = useState<boolean>(false);
// State to control if the chat pane is minimized
const [isMinimized, setIsMinimized] = useState<boolean>(false);
if (!features.enableFloatingChatButton) {
return null;
}
// Function to toggle the chat pane visibility
const toggleChat = () => {
setShowChat(!showChat);

View File

@@ -27,6 +27,7 @@ import {
import { test_property_search } from 'data/mock_property_search';
import { extractLatLon } from 'utils';
import { test_autocomplete } from 'data/mock_autocomplete_results';
import { features } from '../../../../../config/features';
interface AddPropertyDialogProps {
open: boolean;
@@ -204,7 +205,7 @@ const AddPropertyDialog: React.FC<AddPropertyDialogProps> = ({ open, onClose, on
console.log('here we go', value);
if (value) {
console.log('find the test data');
const test: boolean = import.meta.env.USE_LIVE_DATA;
const test: boolean = features.useLiveData;
if (test) {
const parts: string[] =
test_property_search.data.currentMortgages[0].recordingDate.split('T');

View File

@@ -25,6 +25,7 @@ import { PlacePrediction } from './AddPropertyDialog';
import { test_autocomplete } from 'data/mock_autocomplete_results';
import { axiosInstance, axiosRealEstateApi } from '../../../../../axiosApi';
import { extractLatLon } from 'utils';
import { features } from '../../../../../config/features';
interface AttorneyProfileCardProps {
attorney: AttorneyAPI;
@@ -115,7 +116,7 @@ const AttorneyProfileCard: React.FC<AttorneyProfileCardProps> = ({
event: React.SyntheticEvent,
value: string,
) => {
const test: boolean = !import.meta.env.USE_LIVE_DATA;
const test: boolean = !features.useLiveData;
let data: AutocompleteDataResponseAPI[] = [];
if (value.length > 2) {
if (test) {

View File

@@ -0,0 +1,63 @@
/**
* Feature flags and environment configuration
* Controls feature availability and environment settings based on build mode (production, beta, development)
*/
interface FeatureConfig {
// Registration features
enableAttorneyRegistration: boolean;
enableRealEstateAgentRegistration: boolean;
enableRegistration: boolean;
// UI Features
enableFloatingChatButton: boolean;
// API Configuration
apiUrl: string;
// Data Configuration
useLiveData: boolean;
}
/**
* Get feature configuration based on current environment
*/
const getFeatureConfig = (): FeatureConfig => {
const mode = import.meta.env.MODE || 'development';
// Production configuration
if (mode === 'production') {
return {
enableAttorneyRegistration: false,
enableRealEstateAgentRegistration: false,
enableRegistration: false,
enableFloatingChatButton: false,
apiUrl: 'https://backend.ditchtheagent.com/api/',
useLiveData: true,
};
}
// Beta configuration
if (mode === 'beta') {
return {
enableAttorneyRegistration: true,
enableRealEstateAgentRegistration: true,
enableRegistration: true,
enableFloatingChatButton: true,
apiUrl: 'https://beta.backend.ditchtheagent.com/api/',
useLiveData: true,
};
}
// Development configuration (default)
return {
enableAttorneyRegistration: true,
enableRealEstateAgentRegistration: true,
enableRegistration: true,
enableFloatingChatButton: true,
apiUrl: 'http://127.0.0.1:8010/api/',
useLiveData: false,
};
};
export const features = getFeatureConfig();

View File

@@ -1,6 +1,7 @@
import React, { useEffect, createContext, useRef, useState, useContext, ReactNode } from 'react';
import { AccountContext } from './AccountContext';
import { AuthContext } from './AuthContext';
import { features } from '../config/features';
// ---
// Define Types and Interfaces
@@ -65,10 +66,10 @@ interface WebSocketProviderProps {
// Provide a default value that matches the IWebSocketContext interface.
// This is used when a component tries to consume the context without a provider.
const WebSocketContext = createContext<IWebSocketContext>({
subscribe: () => {},
unsubscribe: () => {},
subscribe: () => { },
unsubscribe: () => { },
socket: null,
sendMessages: () => {},
sendMessages: () => { },
});
// ---
@@ -141,7 +142,7 @@ function WebSocketProvider({ children }: WebSocketProviderProps) {
ws.current.close();
}
const wsUrl = new URL(import.meta.env.VITE_API_URL || 'ws://127.0.0.1:8010/ws/');
const wsUrl = new URL(features.apiUrl || 'ws://127.0.0.1:8010/ws/');
wsUrl.protocol = wsUrl.protocol.replace('http', 'ws');
ws.current = new WebSocket(

View File

@@ -20,8 +20,9 @@ import {
Switch,
Typography,
} from '@mui/material';
import { features } from '../../config/features';
const base_url: string = `${import.meta.env.VITE_API_URL?.replace('/api/', '')}/media/vendor_pictures/`;
const base_url: string = `${features.apiUrl?.replace('/api/', '')}/media/vendor_pictures/`;
// Define the array of corrected and alphabetized categories with 'as const'
export const CATEGORY_NAMES = [

View File

@@ -24,6 +24,7 @@ import Image from 'components/base/Image';
import { axiosInstance } from '../../axiosApi.js';
import PasswordStrengthChecker from '../../components/PasswordStrengthChecker';
import { useNavigate } from 'react-router-dom';
import { features } from '../../config/features';
type SignUpValues = {
email: string;
@@ -191,41 +192,43 @@ const SignUp = (): ReactElement => {
</InputLabel>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
{[
{ value: 'property_owner', label: 'Home Buyer/Seller', icon: 'mdi:home-account' },
{ value: 'attorney', label: 'Attorney', icon: 'mdi:gavel' },
{ value: 'vendor', label: 'Vendor', icon: 'mdi:briefcase' },
].map((type) => (
<Paper
key={type.value}
variant="outlined"
onClick={() => setFieldValue('ownerType', type.value)}
sx={{
p: 2,
flex: 1,
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 1,
borderColor: values.ownerType === type.value ? 'primary.main' : 'divider',
bgcolor: values.ownerType === type.value ? 'action.selected' : 'background.paper',
transition: 'all 0.2s',
'&:hover': {
borderColor: 'primary.main',
bgcolor: 'action.hover',
},
}}
>
<IconifyIcon icon={type.icon} width={24} height={24} color={values.ownerType === type.value ? 'primary.main' : 'text.secondary'} />
<Typography
variant="body2"
fontWeight={600}
color={values.ownerType === type.value ? 'primary.main' : 'text.primary'}
{ value: 'property_owner', label: 'Home Buyer/Seller', icon: 'mdi:home-account', enabled: true },
{ value: 'attorney', label: 'Attorney', icon: 'mdi:gavel', enabled: features.enableAttorneyRegistration },
{ value: 'vendor', label: 'Vendor', icon: 'mdi:briefcase', enabled: features.enableRealEstateAgentRegistration },
]
.filter((type) => type.enabled)
.map((type) => (
<Paper
key={type.value}
variant="outlined"
onClick={() => setFieldValue('ownerType', type.value)}
sx={{
p: 2,
flex: 1,
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 1,
borderColor: values.ownerType === type.value ? 'primary.main' : 'divider',
bgcolor: values.ownerType === type.value ? 'action.selected' : 'background.paper',
transition: 'all 0.2s',
'&:hover': {
borderColor: 'primary.main',
bgcolor: 'action.hover',
},
}}
>
{type.label}
</Typography>
</Paper>
))}
<IconifyIcon icon={type.icon} width={24} height={24} color={values.ownerType === type.value ? 'primary.main' : 'text.secondary'} />
<Typography
variant="body2"
fontWeight={600}
color={values.ownerType === type.value ? 'primary.main' : 'text.primary'}
>
{type.label}
</Typography>
</Paper>
))}
</Stack>
</FormControl>