refactor: split monolith into 3 microservices (frontend, backend, discord-gateway)
- Extract services into services/{frontend,backend,discord-gateway}
- Create packages/shared/ for shared logger, errors, utils, types
- Setup Modular MVC pattern in backend (controller→service→repository)
- Setup event-driven architecture in discord-gateway with Redis pub/sub
- Move Docker files to infra/docker/ with per-service Dockerfiles
- Update docker-compose.yml to use Traefik-only routing (no port exposes)
- Update GitHub Actions deploy workflow for multi-service matrix build
- Fix all import paths and resolve type errors across all services
- All 3 services pass tsc --noEmit clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
bda8304bb9
commit
c48a0c5e3b
@@ -0,0 +1,85 @@
|
||||
// ─── Toast notification system ──────────────────────────────────────────────
|
||||
import {
|
||||
createContext,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
interface Toast {
|
||||
id: string;
|
||||
message: string;
|
||||
type: "info" | "success" | "error" | "warning";
|
||||
}
|
||||
|
||||
interface ToastContextType {
|
||||
toasts: Toast[];
|
||||
addToast: (message: string, type?: Toast["type"]) => void;
|
||||
removeToast: (id: string) => void;
|
||||
}
|
||||
|
||||
const ToastContext = createContext<ToastContextType>({
|
||||
toasts: [],
|
||||
addToast: () => {},
|
||||
removeToast: () => {},
|
||||
});
|
||||
|
||||
export function ToastProvider({ children }: { children: ReactNode }) {
|
||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||
|
||||
const addToast = useCallback(
|
||||
(message: string, type: Toast["type"] = "info") => {
|
||||
const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||
setToasts((prev) => [...prev, { id, message, type }]);
|
||||
setTimeout(
|
||||
() => setToasts((prev) => prev.filter((t) => t.id !== id)),
|
||||
4000,
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const removeToast = useCallback((id: string) => {
|
||||
setToasts((prev) => prev.filter((t) => t.id !== id));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ toasts, addToast, removeToast }}>
|
||||
{children}
|
||||
<ToastContainer />
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useToast() {
|
||||
return useContext(ToastContext);
|
||||
}
|
||||
|
||||
function ToastContainer() {
|
||||
const { toasts, removeToast } = useContext(ToastContext);
|
||||
|
||||
if (toasts.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
|
||||
{toasts.map((toast) => (
|
||||
<div
|
||||
key={toast.id}
|
||||
className={`rounded-lg border px-4 py-3 text-sm shadow-lg backdrop-blur-xl cursor-pointer transition-all hover:scale-[1.02] ${
|
||||
toast.type === "error"
|
||||
? "border-destructive/30 bg-destructive/20 text-destructive"
|
||||
: toast.type === "success"
|
||||
? "border-green-500/30 bg-green-500/10 text-green-300"
|
||||
: toast.type === "warning"
|
||||
? "border-yellow-500/30 bg-yellow-500/10 text-yellow-300"
|
||||
: "border-border/30 bg-card/80 text-foreground"
|
||||
}`}
|
||||
onClick={() => removeToast(toast.id)}
|
||||
>
|
||||
{toast.message}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user