Skip to content
Open
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
9 changes: 2 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import useAccessibility from './hooks/useAccessibility';
import PageLayout from './components/PageLayout';

import { ThemeProvider } from './contexts/ThemeContext';
import { config } from './config/env';

const LandingContent: React.FC = () => (
<>
Expand All @@ -42,13 +43,7 @@ function App() {
useAccessibility();

// Support local testing via environment variable
const enableLocalAuth =
process.env.REACT_APP_ENABLE_LOCAL_AUTH === 'true' ||
process.env.REACT_APP_ENABLE_LOCAL_AUTH === '1';

const enableLocalDocs =
process.env.REACT_APP_ENABLE_LOCAL_DOCS === 'true' ||
process.env.REACT_APP_ENABLE_LOCAL_DOCS === '1';
const { enableLocalAuth, enableLocalDocs } = config;

const isDocsHost =
(typeof window !== 'undefined' &&
Expand Down
4 changes: 2 additions & 2 deletions src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import { config } from '../config/env';
import { motion, AnimatePresence } from 'framer-motion';
import { BookOpen, Plus } from 'lucide-react';
import { useLocation } from 'react-router-dom';
Expand All @@ -23,8 +24,7 @@ const Dashboard: React.FC = () => {
const refreshUserData = async () => {
try {
const token = localStorage.getItem('accessToken');
const apiBaseUrl =
process.env.REACT_APP_API_BASE_URL || 'http://localhost:3001';
const apiBaseUrl = config.apiBaseUrl;
const response = await fetch(`${apiBaseUrl}/api/auth/me`, {
headers: {
Authorization: `Bearer ${token}`,
Expand Down
4 changes: 2 additions & 2 deletions src/components/DeviceConnect.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import { config } from '../config/env';
import {
useSearchParams,
useNavigate,
Expand Down Expand Up @@ -84,8 +85,7 @@ const DeviceConnect: React.FC = () => {
setErrorMessage('');

try {
const apiBaseUrl =
process.env.REACT_APP_API_BASE_URL || 'http://localhost:3001';
const apiBaseUrl = config.apiBaseUrl;
const token = localStorage.getItem('accessToken');

const response = await fetch(`${apiBaseUrl}/api/oauth/device/confirm`, {
Expand Down
24 changes: 10 additions & 14 deletions src/components/EarlyAccessForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { config } from '../config/env';
import { motion } from 'framer-motion';
import { CheckCircle, Send, AlertCircle } from 'lucide-react';
import emailjs from '@emailjs/browser';
Expand Down Expand Up @@ -33,20 +34,15 @@ const EarlyAccessForm: React.FC = () => {

try {
// EmailJS configuration
const serviceId =
process.env.REACT_APP_EMAILJS_SERVICE_ID || 'your_service_id';
const welcomeTemplateId =
process.env.REACT_APP_EMAILJS_WELCOME_TEMPLATE_ID ||
'your_welcome_template_id';
const notificationTemplateId =
process.env.REACT_APP_EMAILJS_NOTIFICATION_TEMPLATE_ID ||
'your_notification_template_id';
const publicKey =
process.env.REACT_APP_EMAILJS_PUBLIC_KEY || 'your_public_key';
const fromEmail =
process.env.REACT_APP_FROM_EMAIL || 'hello@refactron.dev';
const notificationEmail =
process.env.REACT_APP_NOTIFICATION_EMAIL || 'hello@refactron.dev';
const { emailjs: emailjsConfig, emails } = config;
const {
serviceId,
welcomeTemplateId,
notificationTemplateId,
publicKey,
} = emailjsConfig;
const fromEmail = emails.from;
const notificationEmail = emails.notification;

// Check if environment variables are properly set
if (
Expand Down
24 changes: 10 additions & 14 deletions src/components/EarlyAccessModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';
import React, { useState } from 'react';
import { config } from '../config/env';
import { motion, AnimatePresence } from 'framer-motion';
import { X, CheckCircle, AlertCircle } from 'lucide-react';
import emailjs from '@emailjs/browser';
Expand Down Expand Up @@ -44,20 +45,15 @@ const EarlyAccessModal: React.FC<EarlyAccessModalProps> = ({
setIsLoading(true);

try {
const serviceId =
process.env.REACT_APP_EMAILJS_SERVICE_ID || 'your_service_id';
const welcomeTemplateId =
process.env.REACT_APP_EMAILJS_WELCOME_TEMPLATE_ID ||
'your_welcome_template_id';
const notificationTemplateId =
process.env.REACT_APP_EMAILJS_NOTIFICATION_TEMPLATE_ID ||
'your_notification_template_id';
const publicKey =
process.env.REACT_APP_EMAILJS_PUBLIC_KEY || 'your_public_key';
const fromEmail =
process.env.REACT_APP_FROM_EMAIL || 'hello@refactron.dev';
const notificationEmail =
process.env.REACT_APP_NOTIFICATION_EMAIL || 'hello@refactron.dev';
const { emailjs: emailjsConfig, emails } = config;
const {
serviceId,
welcomeTemplateId,
notificationTemplateId,
publicKey,
} = emailjsConfig;
const fromEmail = emails.from;
const notificationEmail = emails.notification;

if (
serviceId === 'your_service_id' ||
Expand Down
6 changes: 2 additions & 4 deletions src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { config } from '../config/env';
import { motion } from 'framer-motion';
import { AlertTriangle, RefreshCw, Home } from 'lucide-react';
import * as Sentry from '@sentry/react';
Expand Down Expand Up @@ -32,10 +33,7 @@ class ErrorBoundary extends Component<Props, State> {
});

// Log to Sentry in production if configured
if (
process.env.NODE_ENV === 'production' &&
process.env.REACT_APP_SENTRY_DSN
) {
if (config.isProduction && config.sentryDsn) {
Sentry.captureException(error, {
extra: {
componentStack: errorInfo.componentStack,
Expand Down
4 changes: 2 additions & 2 deletions src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import { config } from '../config/env';
import { motion, AnimatePresence } from 'framer-motion';
import { ArrowRight, Eye, EyeOff } from 'lucide-react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
Expand Down Expand Up @@ -195,8 +196,7 @@ const LoginForm: React.FC = () => {
setIsLoading(true);

try {
const apiBaseUrl =
process.env.REACT_APP_API_BASE_URL || 'http://localhost:3001';
const apiBaseUrl = config.apiBaseUrl;
const response = await fetch(`${apiBaseUrl}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
Expand Down
4 changes: 2 additions & 2 deletions src/components/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import { config } from '../config/env';
import { motion, AnimatePresence } from 'framer-motion';
import { ArrowRight, Eye, EyeOff } from 'lucide-react';
import { Link, useNavigate } from 'react-router-dom';
Expand Down Expand Up @@ -154,8 +155,7 @@ const SignupForm: React.FC = () => {
setIsLoading(true);

try {
const apiBaseUrl =
process.env.REACT_APP_API_BASE_URL || 'http://localhost:3001';
const apiBaseUrl = config.apiBaseUrl;
const response = await fetch(`${apiBaseUrl}/api/auth/signup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
Expand Down
4 changes: 2 additions & 2 deletions src/components/VerifyEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import { config } from '../config/env';
import { useSearchParams, Link, useLocation } from 'react-router-dom';
import { Check, X, ArrowRight } from 'lucide-react';
import { motion } from 'framer-motion';
Expand Down Expand Up @@ -36,8 +37,7 @@ const VerifyEmail: React.FC = () => {
setStatus('loading');
const verifyToken = async () => {
try {
const apiBaseUrl =
process.env.REACT_APP_API_BASE_URL || 'http://localhost:3001';
const apiBaseUrl = config.apiBaseUrl;
const response = await fetch(`${apiBaseUrl}/api/auth/verify-email`, {
method: 'POST',
headers: {
Expand Down
56 changes: 56 additions & 0 deletions src/config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Centralized environment configuration for the Refactron frontend.
* All process.env.REACT_APP_* usages should be moved here.
*/

const getEnv = (name: string, defaultValue = ''): string => {
return process.env[name] || defaultValue;
};

const getBoolEnv = (name: string): boolean => {
const val = process.env[name];
return val === 'true' || val === '1';
};

export const config = {
// Application Environment
isProduction: process.env.NODE_ENV === 'production',
isDevelopment: process.env.NODE_ENV === 'development',
isTest: process.env.NODE_ENV === 'test',

// API Configuration
apiBaseUrl: getEnv('REACT_APP_API_BASE_URL', 'http://localhost:3001'),

// Feature Toggles
enableLocalAuth: getBoolEnv('REACT_APP_ENABLE_LOCAL_AUTH'),
enableLocalDocs: getBoolEnv('REACT_APP_ENABLE_LOCAL_DOCS'),

// OAuth Configuration
googleClientId: getEnv('REACT_APP_GOOGLE_CLIENT_ID'),
githubClientId: getEnv('REACT_APP_GITHUB_CLIENT_ID'),

// Analytics & Monitoring
sentryDsn: getEnv('REACT_APP_SENTRY_DSN'),

// EmailJS Configuration
emailjs: {
serviceId: getEnv('REACT_APP_EMAILJS_SERVICE_ID', 'your_service_id'),
welcomeTemplateId: getEnv(
'REACT_APP_EMAILJS_WELCOME_TEMPLATE_ID',
'your_welcome_template_id'
),
notificationTemplateId: getEnv(
'REACT_APP_EMAILJS_NOTIFICATION_TEMPLATE_ID',
'your_notification_template_id'
),
publicKey: getEnv('REACT_APP_EMAILJS_PUBLIC_KEY', 'your_public_key'),
},

// Contact Emails
emails: {
from: getEnv('REACT_APP_FROM_EMAIL', 'hello@refactron.dev'),
notification: getEnv('REACT_APP_NOTIFICATION_EMAIL', 'hello@refactron.dev'),
},
};

export default config;
3 changes: 2 additions & 1 deletion src/hooks/useRepositories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import { config } from '../config/env';

export interface Repository {
id: number;
Expand Down Expand Up @@ -39,7 +40,7 @@ export function useRepositories(): UseRepositoriesResult {

try {
const response = await fetch(
`${process.env.REACT_APP_API_BASE_URL}/api/github/repositories`,
`${config.apiBaseUrl}/api/github/repositories`,
{
credentials: 'include',
headers: {
Expand Down
6 changes: 3 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import * as Sentry from '@sentry/react';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { config } from './config/env';

// Apply dark theme immediately
if (typeof document !== 'undefined') {
document.documentElement.classList.add('dark');
}

Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
dsn: config.sentryDsn,
environment: process.env.NODE_ENV,
enabled:
process.env.NODE_ENV === 'production' && !!process.env.REACT_APP_SENTRY_DSN,
enabled: config.isProduction && !!config.sentryDsn,
});

const root = ReactDOM.createRoot(
Expand Down
38 changes: 27 additions & 11 deletions src/utils/oauth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ import {
handleOAuthCallback,
isOAuthProviderConfigured,
} from './oauth';
import { config } from '../config/env';

// Mock the config module
jest.mock('../config/env', () => ({
__esModule: true,
config: {
googleClientId: '',
githubClientId: '',
apiBaseUrl: 'http://localhost:3001',
},
default: {
googleClientId: '',
githubClientId: '',
apiBaseUrl: 'http://localhost:3001',
},
}));

// Mock window.location
delete (window as any).location;
Expand Down Expand Up @@ -51,9 +67,9 @@ describe('OAuth utility functions', () => {
beforeEach(() => {
sessionStorage.clear();
window.location.href = '';
// Clear environment variables
delete process.env.REACT_APP_GOOGLE_CLIENT_ID;
delete process.env.REACT_APP_GITHUB_CLIENT_ID;
config.googleClientId = '';
config.githubClientId = '';
config.apiBaseUrl = 'http://localhost:3001';
// Clear fetch mock
global.fetch = jest.fn();
});
Expand All @@ -64,7 +80,7 @@ describe('OAuth utility functions', () => {

describe('isOAuthProviderConfigured', () => {
it('should return true when Google client ID is configured', () => {
process.env.REACT_APP_GOOGLE_CLIENT_ID = 'test-google-client-id';
(config as any).googleClientId = 'test-google-client-id';
expect(isOAuthProviderConfigured('google')).toBe(true);
});

Expand All @@ -73,7 +89,7 @@ describe('OAuth utility functions', () => {
});

it('should return true when GitHub client ID is configured', () => {
process.env.REACT_APP_GITHUB_CLIENT_ID = 'test-github-client-id';
(config as any).githubClientId = 'test-github-client-id';
expect(isOAuthProviderConfigured('github')).toBe(true);
});

Expand Down Expand Up @@ -169,7 +185,7 @@ describe('OAuth utility functions', () => {
});

it('should redirect to Google OAuth URL with correct parameters for login', async () => {
process.env.REACT_APP_GOOGLE_CLIENT_ID = 'test-google-client-id';
(config as any).googleClientId = 'test-google-client-id';

await initiateOAuth('google', 'login', {
redirectUri: 'http://localhost:3000/auth/callback',
Expand All @@ -188,7 +204,7 @@ describe('OAuth utility functions', () => {
});

it('should redirect to Google OAuth URL with consent prompt for signup', async () => {
process.env.REACT_APP_GOOGLE_CLIENT_ID = 'test-google-client-id';
(config as any).googleClientId = 'test-google-client-id';

await initiateOAuth('google', 'signup', {
redirectUri: 'http://localhost:3000/auth/callback',
Expand All @@ -198,7 +214,7 @@ describe('OAuth utility functions', () => {
});

it('should redirect to GitHub OAuth URL with correct parameters for login', async () => {
process.env.REACT_APP_GITHUB_CLIENT_ID = 'test-github-client-id';
(config as any).githubClientId = 'test-github-client-id';

await initiateOAuth('github', 'login', {
redirectUri: 'http://localhost:3000/auth/callback',
Expand All @@ -213,7 +229,7 @@ describe('OAuth utility functions', () => {
});

it('should redirect to GitHub OAuth URL with extended scope for signup', async () => {
process.env.REACT_APP_GITHUB_CLIENT_ID = 'test-github-client-id';
(config as any).githubClientId = 'test-github-client-id';

await initiateOAuth('github', 'signup', {
redirectUri: 'http://localhost:3000/auth/callback',
Expand All @@ -224,7 +240,7 @@ describe('OAuth utility functions', () => {
});

it('should store OAuth state in sessionStorage', async () => {
process.env.REACT_APP_GOOGLE_CLIENT_ID = 'test-google-client-id';
(config as any).googleClientId = 'test-google-client-id';

await initiateOAuth('google', 'login', {
redirectUri: 'http://localhost:3000/auth/callback',
Expand Down Expand Up @@ -324,7 +340,7 @@ describe('OAuth utility functions', () => {
});

it('should use REACT_APP_API_BASE_URL if apiBaseUrl is not provided', async () => {
process.env.REACT_APP_API_BASE_URL = 'http://localhost:4000';
(config as any).apiBaseUrl = 'http://localhost:4000';

global.fetch = jest.fn().mockResolvedValue({
ok: true,
Expand Down
Loading
Loading