diff --git a/apps/seo/src/routeTree.gen.ts b/apps/seo/src/routeTree.gen.ts index 8887313d1..9a28944a5 100644 --- a/apps/seo/src/routeTree.gen.ts +++ b/apps/seo/src/routeTree.gen.ts @@ -13,6 +13,7 @@ import { Route as LoginRouteImport } from './routes/login' import { Route as AuthedRouteRouteImport } from './routes/_authed/route' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiSplatRouteImport } from './routes/api/$' +import { Route as AuthedAdminRouteRouteImport } from './routes/_authed/admin/route' import { Route as AuthedOrganizationSlugRouteRouteImport } from './routes/_authed/$organizationSlug/route' import { Route as AuthedOnboardingIndexRouteImport } from './routes/_authed/onboarding/index' import { Route as AuthedOrganizationSlugIndexRouteImport } from './routes/_authed/$organizationSlug/index' @@ -54,6 +55,11 @@ const ApiSplatRoute = ApiSplatRouteImport.update({ path: '/api/$', getParentRoute: () => rootRouteImport, } as any) +const AuthedAdminRouteRoute = AuthedAdminRouteRouteImport.update({ + id: '/admin', + path: '/admin', + getParentRoute: () => AuthedRouteRoute, +} as any) const AuthedOrganizationSlugRouteRoute = AuthedOrganizationSlugRouteRouteImport.update({ id: '/$organizationSlug', @@ -187,6 +193,7 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/login': typeof LoginRoute '/$organizationSlug': typeof AuthedOrganizationSlugRouteRouteWithChildren + '/admin': typeof AuthedAdminRouteRoute '/api/$': typeof ApiSplatRoute '/$organizationSlug/$projectSlug': typeof AuthedOrganizationSlugProjectSlugRouteRouteWithChildren '/invite/$invitationId': typeof AuthedInviteInvitationIdRoute @@ -212,6 +219,7 @@ export interface FileRoutesByFullPath { export interface FileRoutesByTo { '/': typeof IndexRoute '/login': typeof LoginRoute + '/admin': typeof AuthedAdminRouteRoute '/api/$': typeof ApiSplatRoute '/invite/$invitationId': typeof AuthedInviteInvitationIdRoute '/api/rpc/$': typeof ApiRpcSplatRoute @@ -238,6 +246,7 @@ export interface FileRoutesById { '/_authed': typeof AuthedRouteRouteWithChildren '/login': typeof LoginRoute '/_authed/$organizationSlug': typeof AuthedOrganizationSlugRouteRouteWithChildren + '/_authed/admin': typeof AuthedAdminRouteRoute '/api/$': typeof ApiSplatRoute '/_authed/$organizationSlug/$projectSlug': typeof AuthedOrganizationSlugProjectSlugRouteRouteWithChildren '/_authed/invite/$invitationId': typeof AuthedInviteInvitationIdRoute @@ -266,6 +275,7 @@ export interface FileRouteTypes { | '/' | '/login' | '/$organizationSlug' + | '/admin' | '/api/$' | '/$organizationSlug/$projectSlug' | '/invite/$invitationId' @@ -291,6 +301,7 @@ export interface FileRouteTypes { to: | '/' | '/login' + | '/admin' | '/api/$' | '/invite/$invitationId' | '/api/rpc/$' @@ -316,6 +327,7 @@ export interface FileRouteTypes { | '/_authed' | '/login' | '/_authed/$organizationSlug' + | '/_authed/admin' | '/api/$' | '/_authed/$organizationSlug/$projectSlug' | '/_authed/invite/$invitationId' @@ -377,6 +389,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiSplatRouteImport parentRoute: typeof rootRouteImport } + '/_authed/admin': { + id: '/_authed/admin' + path: '/admin' + fullPath: '/admin' + preLoaderRoute: typeof AuthedAdminRouteRouteImport + parentRoute: typeof AuthedRouteRoute + } '/_authed/$organizationSlug': { id: '/_authed/$organizationSlug' path: '/$organizationSlug' @@ -615,6 +634,7 @@ const AuthedOrganizationSlugRouteRouteWithChildren = interface AuthedRouteRouteChildren { AuthedOrganizationSlugRouteRoute: typeof AuthedOrganizationSlugRouteRouteWithChildren + AuthedAdminRouteRoute: typeof AuthedAdminRouteRoute AuthedInviteInvitationIdRoute: typeof AuthedInviteInvitationIdRoute AuthedOnboardingIndexRoute: typeof AuthedOnboardingIndexRoute } @@ -622,6 +642,7 @@ interface AuthedRouteRouteChildren { const AuthedRouteRouteChildren: AuthedRouteRouteChildren = { AuthedOrganizationSlugRouteRoute: AuthedOrganizationSlugRouteRouteWithChildren, + AuthedAdminRouteRoute: AuthedAdminRouteRoute, AuthedInviteInvitationIdRoute: AuthedInviteInvitationIdRoute, AuthedOnboardingIndexRoute: AuthedOnboardingIndexRoute, } diff --git a/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/strategies/-components/manage-strategy-dialog.tsx b/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/strategies/-components/manage-strategy-dialog.tsx index 12f4bf4ed..13167458b 100644 --- a/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/strategies/-components/manage-strategy-dialog.tsx +++ b/apps/seo/src/routes/_authed/$organizationSlug/$projectSlug/strategies/-components/manage-strategy-dialog.tsx @@ -261,6 +261,7 @@ export function ManageStrategyDialog({ id={`${fieldPrefix}-description`} placeholder="What will you do and how will it work?" rows={8} + value={field.value || ""} /> {fieldState.invalid && ( diff --git a/apps/seo/src/routes/_authed/admin/route.tsx b/apps/seo/src/routes/_authed/admin/route.tsx new file mode 100644 index 000000000..154a128b7 --- /dev/null +++ b/apps/seo/src/routes/_authed/admin/route.tsx @@ -0,0 +1,292 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { Input } from "@rectangular-labs/ui/components/ui/input"; +import { Label } from "@rectangular-labs/ui/components/ui/label"; +import { toast } from "@rectangular-labs/ui/components/ui/sonner"; +import { Textarea } from "@rectangular-labs/ui/components/ui/textarea"; +import { useMutation } from "@tanstack/react-query"; +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { useState } from "react"; +import { getApiClientRq } from "~/lib/api"; + +export const Route = createFileRoute("/_authed/admin")({ + beforeLoad: ({ context }) => { + if (!context.user?.email?.endsWith("fluidposts.com")) { + throw notFound(); + } + }, + component: RouteComponent, +}); + +function RouteComponent() { + const api = getApiClientRq(); + const [organizationSlug, setOrganizationSlug] = useState(""); + const [projectSlug, setProjectSlug] = useState(""); + const [instructions, setInstructions] = useState(""); + const [phaseStrategyName, setPhaseStrategyName] = useState(""); + + const { mutate: triggerOnboarding, isPending } = useMutation( + api.admin.triggerOnboardingTask.mutationOptions({ + onSuccess: () => { + toast.success("Triggered onboarding workflow successfully."); + }, + onError: (error) => { + toast.error(error.message); + }, + }), + ); + + const { mutate: triggerStrategySuggestions, isPending: isPendingStrategy } = + useMutation( + api.admin.triggerStrategySuggestionsTask.mutationOptions({ + onSuccess: () => { + toast.success( + "Triggered strategy suggestions workflow successfully.", + ); + setInstructions(""); + }, + onError: (error) => { + toast.error(error.message); + }, + }), + ); + + const { mutate: triggerStrategyPhase, isPending: isPendingStrategyPhase } = + useMutation( + api.admin.triggerStrategyPhaseGenerationTask.mutationOptions({ + onSuccess: () => { + toast.success( + "Triggered strategy phase generation workflow successfully.", + ); + setPhaseStrategyName(""); + }, + onError: (error) => { + toast.error(error.message); + }, + }), + ); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!organizationSlug.trim()) { + toast.error("Please enter an organization slug"); + return; + } + if (!projectSlug.trim()) { + toast.error("Please enter a project slug"); + return; + } + triggerOnboarding({ organizationSlug, projectSlug }); + }; + + const handleStrategySubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!organizationSlug.trim()) { + toast.error("Please enter an organization slug"); + return; + } + if (!projectSlug.trim()) { + toast.error("Please enter a project slug"); + return; + } + triggerStrategySuggestions({ + organizationSlug, + projectSlug, + instructions, + }); + }; + + const handleStrategyPhaseSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!organizationSlug.trim()) { + toast.error("Please enter an organization slug"); + return; + } + if (!projectSlug.trim()) { + toast.error("Please enter a project slug"); + return; + } + if (!phaseStrategyName.trim()) { + toast.error("Please enter a strategy name"); + return; + } + + triggerStrategyPhase({ + organizationSlug, + projectSlug, + strategyName: phaseStrategyName, + }); + }; + + return ( +
+
+

Admin

+

+ Internal tools for the fluidposts team. +

+
+ +
+
+

+ Trigger Onboarding Task +

+

+ Trigger the onboarding workflow in the api-seo package for a + specific project. +

+
+
+
+
+ + setOrganizationSlug(e.target.value)} + placeholder="e.g. acme" + value={organizationSlug} + /> +
+
+ + setProjectSlug(e.target.value)} + placeholder="e.g. acme-corp" + value={projectSlug} + /> +
+ +
+
+
+ +
+
+

+ Trigger Strategy Suggestions Task +

+

+ Trigger the strategy suggestions workflow in the api-seo package for + a specific project. +

+
+
+
+
+ + setOrganizationSlug(e.target.value)} + placeholder="e.g. acme" + value={organizationSlug} + /> +
+
+ + setProjectSlug(e.target.value)} + placeholder="e.g. acme-corp" + value={projectSlug} + /> +
+
+ +