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:
@@ -0,0 +1,144 @@
|
||||
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Alert, AlertDescription } from './Alert';
|
||||
|
||||
export type ToastVariant = 'default' | 'success' | 'error' | 'warning' | 'info';
|
||||
|
||||
export interface Toast {
|
||||
id: string;
|
||||
title?: string;
|
||||
message: string;
|
||||
variant: ToastVariant;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
interface ToastContextType {
|
||||
toasts: Toast[];
|
||||
addToast: (toast: Omit<Toast, 'id'>) => void;
|
||||
removeToast: (id: string) => void;
|
||||
success: (message: string, title?: string) => void;
|
||||
error: (message: string, title?: string) => void;
|
||||
warning: (message: string, title?: string) => void;
|
||||
info: (message: string, title?: string) => void;
|
||||
}
|
||||
|
||||
const ToastContext = createContext<ToastContextType | undefined>(undefined);
|
||||
|
||||
export function useToast() {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) {
|
||||
throw new Error('useToast must be used within a ToastProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function ToastProvider({ children }: { children: ReactNode }) {
|
||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||
|
||||
const removeToast = useCallback((id: string) => {
|
||||
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
||||
}, []);
|
||||
|
||||
const addToast = useCallback(
|
||||
(toast: Omit<Toast, 'id'>) => {
|
||||
const id = Math.random().toString(36).substring(2, 9);
|
||||
const newToast: Toast = {
|
||||
...toast,
|
||||
id,
|
||||
duration: toast.duration || 5000,
|
||||
};
|
||||
|
||||
setToasts((prev) => [...prev, newToast]);
|
||||
|
||||
if (newToast.duration && newToast.duration > 0) {
|
||||
setTimeout(() => {
|
||||
removeToast(id);
|
||||
}, newToast.duration);
|
||||
}
|
||||
},
|
||||
[removeToast]
|
||||
);
|
||||
|
||||
const success = useCallback(
|
||||
(message: string, title?: string) => {
|
||||
addToast({ message, title, variant: 'success' });
|
||||
},
|
||||
[addToast]
|
||||
);
|
||||
|
||||
const error = useCallback(
|
||||
(message: string, title?: string) => {
|
||||
addToast({ message, title, variant: 'error' });
|
||||
},
|
||||
[addToast]
|
||||
);
|
||||
|
||||
const warning = useCallback(
|
||||
(message: string, title?: string) => {
|
||||
addToast({ message, title, variant: 'warning' });
|
||||
},
|
||||
[addToast]
|
||||
);
|
||||
|
||||
const info = useCallback(
|
||||
(message: string, title?: string) => {
|
||||
addToast({ message, title, variant: 'info' });
|
||||
},
|
||||
[addToast]
|
||||
);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ toasts, addToast, removeToast, success, error, warning, info }}>
|
||||
{children}
|
||||
<ToastContainer toasts={toasts} removeToast={removeToast} />
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function ToastContainer({ toasts, removeToast }: { toasts: Toast[]; removeToast: (id: string) => void }) {
|
||||
if (toasts.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed top-4 right-4 z-50 flex flex-col gap-2 w-full max-w-sm">
|
||||
{toasts.map((toast) => (
|
||||
<ToastItem key={toast.id} toast={toast} onRemove={removeToast} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ToastItem({ toast, onRemove }: { toast: Toast; onRemove: (id: string) => void }) {
|
||||
const variantMap: Record<ToastVariant, { alert: 'default' | 'destructive' | 'success' | 'warning'; icon: string }> = {
|
||||
default: { alert: 'default', icon: 'ℹ️' },
|
||||
success: { alert: 'success', icon: '✓' },
|
||||
error: { alert: 'destructive', icon: '✗' },
|
||||
warning: { alert: 'warning', icon: '⚠' },
|
||||
info: { alert: 'default', icon: 'ℹ️' },
|
||||
};
|
||||
|
||||
const variant = variantMap[toast.variant];
|
||||
|
||||
return (
|
||||
<Alert
|
||||
variant={variant.alert}
|
||||
className="animate-in slide-in-from-top-5 fade-in shadow-lg"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
{toast.title && (
|
||||
<AlertDescription className="font-semibold mb-1">{toast.title}</AlertDescription>
|
||||
)}
|
||||
<AlertDescription>{toast.message}</AlertDescription>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onRemove(toast.id)}
|
||||
className="ml-4 text-gray-500 hover:text-gray-700 text-xl leading-none"
|
||||
aria-label="Close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user