remove user / auth

This commit is contained in:
Matt Beaudoin
2026-03-24 21:22:39 -07:00
parent 9cde325a02
commit 9be82e9e95
20 changed files with 39 additions and 407 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# API base URL for the backend that wraps the abra CLI
VITE_API_URL=http://localhost:3000/api
# Set to 'true' to bypass authentication for development
# Set to 'true' to use mock data
VITE_MOCK_AUTH=true
-14
View File
@@ -1,14 +0,0 @@
[build]
command = "npm run build"
publish = "dist"
# Environment variables for build
[build.environment]
VITE_MOCK_AUTH = "true"
VITE_API_URL = "http://localhost:3000/api"
# Redirect all requests to index.html for client-side routing
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
+12 -23
View File
@@ -1,35 +1,24 @@
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import { LoginForm } from './routes/Login/LoginForm';
import { Authenticated } from './components/Authenticated';
import { Dashboard } from './routes/Authenticated/Dashboard/Dashboard';
import { Apps } from './routes/Authenticated/Apps/Apps';
import { AppDetail } from './routes/Authenticated/Apps/App';
import { Servers } from './routes/Authenticated/Servers/Servers';
import { Server } from './routes/Authenticated/Servers/Server';
import { Dashboard } from './routes/Dashboard/Dashboard';
import { Apps } from './routes/Apps/Apps';
import { AppDetail } from './routes/Apps/App';
import { Servers } from './routes/Servers/Servers';
import { Server } from './routes/Servers/Server';
function App() {
return (
<BrowserRouter>
<AuthProvider>
<Routes>
{/* Public routes */}
<Route path="/login" element={<LoginForm />} />
{/* Protected routes */}
<Route element={<Authenticated />}>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/apps" element={<Apps />} />
<Route path="/apps/:server/:appName" element={<AppDetail />} />
<Route path="/servers" element={<Servers />} />
<Route path="/servers/:serverName" element={<Server />} />
</Route>
<Routes>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/apps" element={<Apps />} />
<Route path="/apps/:server/:appName" element={<AppDetail />} />
<Route path="/servers" element={<Servers />} />
<Route path="/servers/:serverName" element={<Server />} />
{/* 404 catch-all */}
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</AuthProvider>
</BrowserRouter>
);
}
-24
View File
@@ -1,24 +0,0 @@
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
export const Authenticated: React.FC = () => {
const { isAuthenticated, loading } = useAuth();
console.log('Authenticated:', { isAuthenticated, loading });
if (loading) {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh'
}}>
<p>Loading...</p>
</div>
);
}
return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />;
};
-14
View File
@@ -1,6 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
import './_Header.scss';
import logo from '../../assets/coopcloud_logo_grey.svg';
@@ -10,13 +9,7 @@ interface HeaderProps {
}
export const Header: React.FC<HeaderProps> = ({ children }) => {
const { user, logout } = useAuth();
const navigate = useNavigate();
const handleLogout = async () => {
await logout();
navigate('/login');
};
return(
<header className="layout-header">
<div className="header-content">
@@ -34,13 +27,6 @@ return(
Servers
</button>
</nav>
<div className="user-menu">
<span className="username">{user?.username}</span>
<button onClick={handleLogout} className="logout-button">
Logout
</button>
</div>
</div>
</header>
)}
-46
View File
@@ -78,50 +78,4 @@
font-size: $font-size-xl;
}
}
.user-menu {
display: flex;
align-items: center;
gap: $spacing-lg;
@media (max-width: 768px) {
width: 100%;
justify-content: space-between;
}
.username {
font-weight: $font-weight-medium;
font-size: $font-size-base;
@media (max-width: 480px) {
font-size: $font-size-sm;
}
}
.logout-button {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.4);
color: $text-primary;
padding: $spacing-sm $spacing-lg;
border-radius: $radius-md;
cursor: pointer;
transition: all $transition-base;
font-weight: $font-weight-medium;
&:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
&:active {
transform: translateY(0);
}
@media (max-width: 480px) {
padding: $spacing-sm $spacing-md;
font-size: $font-size-sm;
}
}
}
}
-77
View File
@@ -1,77 +0,0 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { apiService } from '../services/api';
import type { User } from '../types';
interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
loading: boolean;
login: (credentials: { username: string; password: string }) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
interface AuthProviderProps {
children: ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [loading, setLoading] = useState(true);
// Check for existing session on mount
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
try {
const parsedUser = JSON.parse(storedUser);
setUser(parsedUser);
setIsAuthenticated(true);
} catch (err) {
console.error('Failed to parse stored user:', err);
localStorage.removeItem('user');
}
}
setLoading(false);
}, []);
const login = async (credentials: { username: string; password: string }) => {
const response = await apiService.login(credentials);
setUser(response.user);
setIsAuthenticated(true);
localStorage.setItem('user', JSON.stringify(response.user));
};
const logout = () => {
apiService.logout();
setUser(null);
setIsAuthenticated(false);
localStorage.removeItem('user');
};
return (
<AuthContext.Provider
value={{
user,
isAuthenticated,
loading,
login,
logout,
}}
>
{children}
</AuthContext.Provider>
);
};
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '../../../components/Header/Header';
import { Terminal } from '../../../components/Terminal/Terminal';
import { apiService } from '../../../services/api';
import type { AbraApp } from '../../../types';
import type { LogEntry } from '../../../services/mockApi';
import { Header } from '../../components/Header/Header';
import { Terminal } from '../../components/Terminal/Terminal';
import { apiService } from '../../services/api';
import type { AbraApp } from '../../types';
import type { LogEntry } from '../../services/mockApi';
import './App.scss';
export const AppDetail: React.FC = () => {
@@ -26,7 +26,7 @@ export const AppDetail: React.FC = () => {
const fetchApp = async () => {
try {
if (isMockMode) {
const { mockApiService } = await import('../../../services/mockApi');
const { mockApiService } = await import('../../services/mockApi');
const appsData = await mockApiService.getAppsGrouped();
const serverApps = appsData[server || ''];
@@ -67,7 +67,7 @@ export const AppDetail: React.FC = () => {
try {
if (isMockMode) {
const { mockApiService } = await import('../../../services/mockApi');
const { mockApiService } = await import('../../services/mockApi');
const onLog = (log: LogEntry) => {
setTerminalLogs(prev => [...prev, log]);
@@ -1,8 +1,8 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Header } from '../../../components/Header/Header';
import { apiService } from '../../../services/api';
import type { AbraApp, AppWithServer, ServerAppsResponse } from '../../../types';
import { Header } from '../../components/Header/Header';
import { apiService } from '../../services/api';
import type { AbraApp, AppWithServer, ServerAppsResponse } from '../../types';
import './Apps.scss';
export const Apps: React.FC = () => {
@@ -21,7 +21,7 @@ export const Apps: React.FC = () => {
const fetchData = async () => {
try {
if (isMockMode) {
const { mockApiService } = await import('../../../services/mockApi');
const { mockApiService } = await import('../../services/mockApi');
const data = await mockApiService.getAppsGrouped();
setAppsData(data);
} else {
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react';
import { Header } from '../../../components/Header/Header';
import { Header } from '../../components/Header/Header';
import { useNavigate } from 'react-router-dom';
import { apiService } from '../../../services/api';
import type { AbraApp, AbraServer } from '../../../types';
import { apiService } from '../../services/api';
import type { AbraApp, AbraServer } from '../../types';
import './_Dashboard.scss';
export const Dashboard: React.FC = () => {
@@ -19,7 +19,7 @@ export const Dashboard: React.FC = () => {
try {
if (isMockMode) {
// Use mock API in development
const { mockApiService } = await import('../../../services/mockApi');
const { mockApiService } = await import('../../services/mockApi');
const [appsData, serversData] = await Promise.all([
mockApiService.getAppsGrouped(),
mockApiService.getServers(),
-94
View File
@@ -1,94 +0,0 @@
@use '../../assets/scss/variables' as *;
@use '../../assets/scss/mixins' as *;
.login-container {
@include flex-center;
min-height: 100vh;
width: 100%;
// @include gradient-primary;
background-color: $primary-light;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.login-card {
@include card;
width: 100%;
max-width: 400px;
h1 {
margin: 0 0 $spacing-sm;
font-size: $font-size-3xl;
color: $text-primary;
text-align: center;
}
.login-subtitle {
text-align: center;
color: $text-secondary;
margin: 0 0 $spacing-2xl;
font-size: $font-size-sm;
}
}
.form-group {
margin-bottom: $spacing-lg;
label {
display: block;
margin-bottom: $spacing-sm;
color: $text-primary;
font-weight: $font-weight-medium;
font-size: $font-size-sm;
}
input {
width: 100%;
padding: $spacing-md;
border: 2px solid $border-color;
border-radius: $radius-md;
font-size: $font-size-base;
transition: border-color $transition-base;
box-sizing: border-box;
&:focus {
outline: none;
border-color: $primary;
}
&:disabled {
background-color: $bg-secondary;
cursor: not-allowed;
}
}
}
.error-message {
// background-color: rgba($error, 0.1);
// color: darken($error, 10%);
padding: $spacing-md;
border-radius: $radius-md;
margin-bottom: $spacing-md;
font-size: $font-size-sm;
// border-left: 4px solid $error;
}
.login-button {
@include button-base;
@include gradient-primary;
width: 100%;
color: white;
font-size: $font-size-base;
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba($primary, 0.4);
}
&:active:not(:disabled) {
transform: translateY(0);
}
}
-72
View File
@@ -1,72 +0,0 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
import './LoginForm.scss';
export const LoginForm: React.FC = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setLoading(true);
try {
await login({ username, password });
navigate('/dashboard');
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed');
} finally {
setLoading(false);
}
};
return (
<div className="login-container">
<div className="login-card">
<h1>Coop Cloud</h1>
<p className="login-subtitle">Sign in to manage your applications</p>
<form onSubmit={handleSubmit}>
{error && <div className="error-message">{error}</div>}
<div className="form-group">
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
autoComplete="username"
disabled={loading}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
autoComplete="current-password"
disabled={loading}
/>
</div>
<button type="submit" disabled={loading} className="login-button">
{loading ? 'Signing in...' : 'Sign in'}
</button>
</form>
</div>
</div>
);
};
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '../../../components/Header/Header';
import { Terminal } from '../../../components/Terminal/Terminal';
import { apiService } from '../../../services/api';
import type { AbraServer, ServerAppsResponse } from '../../../types';
import type { LogEntry } from '../../../services/mockApi';
import { Header } from '../../components/Header/Header';
import { Terminal } from '../../components/Terminal/Terminal';
import { apiService } from '../../services/api';
import type { AbraServer, ServerAppsResponse } from '../../types';
import type { LogEntry } from '../../services/mockApi';
import './Server.scss';
interface ServerWithApps extends AbraServer {
@@ -35,7 +35,7 @@ export const Server: React.FC = () => {
const fetchServer = async () => {
try {
if (isMockMode) {
const { mockApiService } = await import('../../../services/mockApi');
const { mockApiService } = await import('../../services/mockApi');
const appsData = await mockApiService.getAppsGrouped();
const serversData = await mockApiService.getServers();
@@ -95,7 +95,7 @@ export const Server: React.FC = () => {
try {
if (isMockMode) {
const { mockApiService } = await import('../../../services/mockApi');
const { mockApiService } = await import('../../services/mockApi');
const onLog = (log: LogEntry) => {
setTerminalLogs(prev => [...prev, log]);
@@ -1,8 +1,8 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Header } from '../../../components/Header/Header';
import { apiService } from '../../../services/api';
import type { AbraServer, ServerAppsResponse } from '../../../types';
import { Header } from '../../components/Header/Header';
import { apiService } from '../../services/api';
import type { AbraServer, ServerAppsResponse } from '../../types';
import './Servers.scss';
interface ServerWithStats extends AbraServer {
@@ -27,7 +27,7 @@ export const Servers: React.FC = () => {
const fetchData = async () => {
try {
if (isMockMode) {
const { mockApiService } = await import('../../../services/mockApi');
const { mockApiService } = await import('../../services/mockApi');
const [serversData, appsData] = await Promise.all([
mockApiService.getServers(),
mockApiService.getAppsGrouped(),
-16
View File
@@ -1,19 +1,3 @@
export interface User {
id: string;
username: string;
email: string;
}
export interface AuthResponse {
user: User;
token: string;
}
export interface LoginCredentials {
username: string;
password: string;
}
export interface ApiError {
message: string;
status: number;