forked from BornDeleuze/coop-cloud-front
remove user / auth
This commit is contained in:
+1
-1
@@ -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
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 />;
|
||||
};
|
||||
@@ -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>
|
||||
)}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
+4
-4
@@ -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(),
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user