google-oauth-cli-generator
Version:
CLI tool to quickly set up Google OAuth authentication for hackathons and projects
400 lines (364 loc) • 10.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateReactTemplate = generateReactTemplate;
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
async function generateReactTemplate(data) {
const { projectPath } = data;
const frontendPath = path_1.default.join(projectPath, 'frontend');
// Create frontend directory structure
await fs_extra_1.default.ensureDir(path_1.default.join(frontendPath, 'src', 'components'));
await fs_extra_1.default.ensureDir(path_1.default.join(frontendPath, 'public'));
// Generate package.json
const packageJson = {
name: `${data.config.projectName}-frontend`,
version: '1.0.0',
private: true,
dependencies: {
'react': '^18.2.0',
'react-dom': '^18.2.0',
'axios': '^1.5.0',
'react-router-dom': '^6.15.0'
},
devDependencies: {
'@types/react': '^18.2.22',
'@types/react-dom': '^18.2.7',
'@vitejs/plugin-react': '^4.0.4',
'typescript': '^5.2.2',
'vite': '^4.4.9'
},
scripts: {
'dev': 'vite',
'build': 'tsc && vite build',
'preview': 'vite preview'
}
};
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'package.json'), JSON.stringify(packageJson, null, 2));
// Generate vite.config.ts
const viteConfig = `import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
}
})`;
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'vite.config.ts'), viteConfig);
// Generate tsconfig.json
const tsConfig = {
compilerOptions: {
target: 'ES2020',
useDefineForClassFields: true,
lib: ['ES2020', 'DOM', 'DOM.Iterable'],
module: 'ESNext',
skipLibCheck: true,
moduleResolution: 'bundler',
allowImportingTsExtensions: true,
resolveJsonModule: true,
isolatedModules: true,
noEmit: true,
jsx: 'react-jsx',
strict: true,
noUnusedLocals: true,
noUnusedParameters: true,
noFallthroughCasesInSwitch: true
},
include: ['src'],
references: [{ path: './tsconfig.node.json' }]
};
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
// Generate index.html
const indexHtml = `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${data.config.projectName}</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>`;
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'index.html'), indexHtml);
// Generate main.tsx
const mainTsx = `import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)`;
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'src', 'main.tsx'), mainTsx);
// Generate App.tsx
const appTsx = `import { useState, useEffect } from 'react'
import LoginButton from './components/LoginButton'
import UserProfile from './components/UserProfile'
import './App.css'
interface User {
id: string;
name: string;
email: string;
picture: string;
}
function App() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
checkAuthStatus();
}, []);
const checkAuthStatus = async () => {
try {
const response = await fetch('/api/auth/user', {
credentials: 'include'
});
if (response.ok) {
const userData = await response.json();
setUser(userData);
}
} catch (error) {
console.error('Auth check failed:', error);
} finally {
setLoading(false);
}
};
const handleLogin = () => {
window.location.href = '/api/auth/google';
};
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
setUser(null);
} catch (error) {
console.error('Logout failed:', error);
}
};
if (loading) {
return (
<div className="app">
<div className="loading">Loading...</div>
</div>
);
}
return (
<div className="app">
<header className="app-header">
<h1>🚀 ${data.config.projectName}</h1>
<p>Google OAuth Authentication Demo</p>
</header>
<main className="app-main">
{user ? (
<UserProfile user={user} onLogout={handleLogout} />
) : (
<LoginButton onLogin={handleLogin} />
)}
</main>
</div>
)
}
export default App`;
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'src', 'App.tsx'), appTsx);
// Generate LoginButton component
const loginButtonTsx = `interface LoginButtonProps {
onLogin: () => void;
}
function LoginButton({ onLogin }: LoginButtonProps) {
return (
<div className="login-container">
<h2>Welcome! Please sign in</h2>
<button
className="google-login-btn"
onClick={onLogin}
>
<svg className="google-icon" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
Sign in with Google
</button>
</div>
);
}
export default LoginButton`;
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'src', 'components', 'LoginButton.tsx'), loginButtonTsx);
// Generate UserProfile component
const userProfileTsx = `interface User {
id: string;
name: string;
email: string;
picture: string;
}
interface UserProfileProps {
user: User;
onLogout: () => void;
}
function UserProfile({ user, onLogout }: UserProfileProps) {
return (
<div className="profile-container">
<div className="profile-card">
<img
src={user.picture}
alt={user.name}
className="profile-picture"
/>
<h2>Welcome, {user.name}!</h2>
<p className="profile-email">{user.email}</p>
<button
className="logout-btn"
onClick={onLogout}
>
Sign Out
</button>
</div>
</div>
);
}
export default UserProfile`;
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'src', 'components', 'UserProfile.tsx'), userProfileTsx);
// Generate CSS files
const appCss = `
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg,
color: white;
}
.app-header h1 {
font-size: 3rem;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.app-header p {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 2rem;
}
.loading {
font-size: 1.5rem;
animation: pulse 1.5s ease-in-out infinite alternate;
}
@keyframes pulse {
from { opacity: 0.6; }
to { opacity: 1; }
}
.login-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 3rem;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.login-container h2 {
margin-bottom: 2rem;
font-size: 1.8rem;
}
.google-login-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
background: white;
color:
border: none;
border-radius: 12px;
padding: 16px 32px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.google-login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.2);
}
.google-icon {
width: 20px;
height: 20px;
}
.profile-container {
display: flex;
justify-content: center;
align-items: center;
}
.profile-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 3rem;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
text-align: center;
}
.profile-picture {
width: 120px;
height: 120px;
border-radius: 50%;
margin-bottom: 1.5rem;
border: 4px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.profile-card h2 {
margin-bottom: 0.5rem;
font-size: 2rem;
}
.profile-email {
opacity: 0.8;
margin-bottom: 2rem;
font-size: 1.1rem;
}
.logout-btn {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
padding: 12px 24px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
}
.logout-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}`;
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'src', 'App.css'), appCss);
const indexCss = `body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
}`;
await fs_extra_1.default.writeFile(path_1.default.join(frontendPath, 'src', 'index.css'), indexCss);
}
//# sourceMappingURL=react-template.js.map