feat: implement naming convention, deployment automation, and infrastructure updates
- Add comprehensive naming convention (provider-region-resource-env-purpose) - Implement Terraform locals for centralized naming - Update all Terraform resources to use new naming convention - Create deployment automation framework (18 phase scripts) - Add Azure setup scripts (provider registration, quota checks) - Update deployment scripts config with naming functions - Create complete deployment documentation (guide, steps, quick reference) - Add frontend portal implementations (public and internal) - Add UI component library (18 components) - Enhance Entra VerifiedID integration with file utilities - Add API client package for all services - Create comprehensive documentation (naming, deployment, next steps) Infrastructure: - Resource groups, storage accounts with new naming - Terraform configuration updates - Outputs with naming convention examples Deployment: - Automated deployment scripts for all 15 phases - State management and logging - Error handling and validation Documentation: - Naming convention guide and implementation summary - Complete deployment guide (296 steps) - Next steps and quick start guides - Azure prerequisites and setup completion docs Note: ESLint warnings present - will be addressed in follow-up commit
This commit is contained in:
126
apps/portal-public/src/lib/auth.ts
Normal file
126
apps/portal-public/src/lib/auth.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// Simple auth store without external dependencies
|
||||
// In production, this would use Zustand or a proper auth library
|
||||
interface AuthUser {
|
||||
id: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
did?: string;
|
||||
roles?: string[];
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
user: AuthUser | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
login: (user: AuthUser) => void;
|
||||
logout: () => void;
|
||||
setUser: (user: AuthUser | null) => void;
|
||||
}
|
||||
|
||||
// Simple in-memory store with localStorage persistence
|
||||
class AuthStore {
|
||||
private state: AuthState = {
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
setUser: () => {},
|
||||
};
|
||||
|
||||
private listeners: Set<(state: AuthState) => void> = new Set();
|
||||
|
||||
constructor() {
|
||||
this.loadFromStorage();
|
||||
}
|
||||
|
||||
private loadFromStorage() {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
const stored = localStorage.getItem('auth-storage');
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
this.state.user = parsed.user || null;
|
||||
this.state.isAuthenticated = !!this.state.user;
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
|
||||
private saveToStorage() {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
localStorage.setItem('auth-storage', JSON.stringify({ user: this.state.user }));
|
||||
} catch {
|
||||
// Ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
private notify() {
|
||||
this.listeners.forEach((listener) => listener(this.state));
|
||||
}
|
||||
|
||||
subscribe(listener: (state: AuthState) => void) {
|
||||
this.listeners.add(listener);
|
||||
return () => {
|
||||
this.listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
setState(updates: Partial<AuthState>) {
|
||||
this.state = { ...this.state, ...updates };
|
||||
this.saveToStorage();
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
const authStore = typeof window !== 'undefined' ? new AuthStore() : null;
|
||||
|
||||
// React hook to use auth state
|
||||
export function useAuth(): AuthState {
|
||||
const [state, setState] = React.useState<AuthState>(
|
||||
authStore?.getState() || {
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
setUser: () => {},
|
||||
}
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!authStore) return;
|
||||
|
||||
const unsubscribe = authStore.subscribe(setState);
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...state,
|
||||
login: (user: AuthUser) => {
|
||||
authStore?.setState({ user, isAuthenticated: true });
|
||||
if (user.accessToken) {
|
||||
localStorage.setItem('auth_token', user.accessToken);
|
||||
}
|
||||
},
|
||||
logout: () => {
|
||||
authStore?.setState({ user: null, isAuthenticated: false });
|
||||
localStorage.removeItem('auth_token');
|
||||
},
|
||||
setUser: (user: AuthUser | null) => {
|
||||
authStore?.setState({ user, isAuthenticated: !!user });
|
||||
},
|
||||
};
|
||||
}
|
||||
27
apps/portal-public/src/lib/providers.tsx
Normal file
27
apps/portal-public/src/lib/providers.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { ToastProvider } from '@the-order/ui';
|
||||
|
||||
export function Providers({ children }: { children: ReactNode }) {
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60 * 1000, // 1 minute
|
||||
refetchOnWindowFocus: false,
|
||||
retry: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ToastProvider>{children}</ToastProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user