env context and refactor api support different ports

This commit is contained in:
Milo Schwartz 2024-12-12 22:46:58 -05:00
parent d79760dad9
commit d3d2fe398b
No known key found for this signature in database
35 changed files with 287 additions and 135 deletions

View file

@ -1,4 +1,5 @@
{
"tabWidth": 4,
"printWidth": 80
"printWidth": 80,
"trailingComma": "none"
}

View file

@ -3,10 +3,15 @@ import cors from "cors";
import cookieParser from "cookie-parser";
import config from "@server/config";
import logger from "@server/logger";
import { errorHandlerMiddleware, notFoundMiddleware, rateLimitMiddleware } from "@server/middlewares";
import {
errorHandlerMiddleware,
notFoundMiddleware,
rateLimitMiddleware,
} from "@server/middlewares";
import { authenticated, unauthenticated } from "@server/routers/external";
import { router as wsRouter, handleWSUpgrade } from "@server/routers/ws";
import { logIncomingMiddleware } from "./middlewares/logIncoming";
import helmet from "helmet";
const dev = process.env.ENVIRONMENT !== "prod";
const externalPort = config.server.external_port;
@ -16,7 +21,17 @@ export function createApiServer() {
// Middleware setup
apiServer.set("trust proxy", 1);
if (dev) {
apiServer.use(
cors({
origin: `http://localhost:${config.server.next_port}`,
credentials: true,
}),
);
} else {
apiServer.use(cors());
apiServer.use(helmet());
}
apiServer.use(cookieParser());
apiServer.use(express.json());
@ -26,7 +41,7 @@ export function createApiServer() {
windowMin: config.rate_limit.window_minutes,
max: config.rate_limit.max_requests,
type: "IP_ONLY",
})
}),
);
}
@ -46,7 +61,9 @@ export function createApiServer() {
// Create HTTP server
const httpServer = apiServer.listen(externalPort, (err?: any) => {
if (err) throw err;
logger.info(`API server is running on http://localhost:${externalPort}`);
logger.info(
`API server is running on http://localhost:${externalPort}`,
);
});
// Handle WebSocket upgrades

View file

@ -124,6 +124,7 @@ if (!parsedConfig.success) {
throw new Error(`Invalid configuration file: ${errors}`);
}
process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
process.env.SERVER_EXTERNAL_PORT =
parsedConfig.data.server.external_port.toString();
process.env.SERVER_INTERNAL_PORT =

View file

@ -15,7 +15,7 @@ async function startServers() {
return {
apiServer,
nextServer,
internalServer
internalServer,
};
}

View file

@ -1,33 +1,53 @@
import axios from "axios";
import { env } from "@app/lib/types/env";
import axios, { AxiosInstance } from "axios";
let origin;
if (typeof window !== "undefined") {
origin = window.location.origin;
}
let apiInstance: AxiosInstance | null = null;
export const api = axios.create({
baseURL: `${origin}/api/v1`,
export function createApiClient({ env }: { env: env }): AxiosInstance {
if (apiInstance) {
return apiInstance;
}
let baseURL;
const suffix = "api/v1";
if (window.location.port === env.NEXT_PORT) {
// this means the user is addressing the server directly
baseURL = `${window.location.protocol}//${window.location.hostname}:${env.SERVER_EXTERNAL_PORT}/${suffix}`;
axios.defaults.withCredentials = true;
} else {
// user is accessing through a proxy
baseURL = window.location.origin + `/${suffix}`;
}
if (!baseURL) {
throw new Error("Failed to create api client, invalid environment");
}
apiInstance = axios.create({
baseURL,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
"Content-Type": "application/json"
}
});
return apiInstance;
}
// we can pull from env var here becuase it is only used in the server
export const internal = axios.create({
baseURL: `http://localhost:${process.env.SERVER_EXTERNAL_PORT}/api/v1`,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
"Content-Type": "application/json"
}
});
export const priv = axios.create({
baseURL: `http://localhost:${process.env.SERVER_INTERNAL_PORT}/api/v1`,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
"Content-Type": "application/json"
}
});
export default api;

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import { Button } from "@app/components/ui/button";
import {
Form,
@ -30,6 +29,8 @@ import {
import { useOrgContext } from "@app/hooks/useOrgContext";
import { CreateRoleBody, CreateRoleResponse } from "@server/routers/role";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
type CreateRoleFormProps = {
open: boolean;
@ -52,6 +53,8 @@ export default function CreateRoleForm({
const [loading, setLoading] = useState(false);
const api = createApiClient(useEnvContext());
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import { Button } from "@app/components/ui/button";
import {
Form,
@ -37,6 +36,8 @@ import {
} from "@app/components/ui/select";
import { RoleRow } from "./RolesTable";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
type CreateRoleFormProps = {
open: boolean;
@ -61,6 +62,8 @@ export default function DeleteRoleForm({
const [loading, setLoading] = useState(false);
const [roles, setRoles] = useState<ListRolesResponse["roles"]>([]);
const api = createApiClient(useEnvContext());
useEffect(() => {
async function fetchRoles() {
const res = await api

View file

@ -11,13 +11,14 @@ import { Button } from "@app/components/ui/button";
import { ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
import { useState } from "react";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import api from "@app/api";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { useToast } from "@app/hooks/useToast";
import { RolesDataTable } from "./RolesDataTable";
import { Role } from "@server/db/schema";
import CreateRoleForm from "./CreateRoleForm";
import DeleteRoleForm from "./DeleteRoleForm";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
export type RoleRow = Role;
@ -33,6 +34,8 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
const [roleToRemove, setUserToRemove] = useState<RoleRow | null>(null);
const api = createApiClient(useEnvContext());
const { org } = useOrgContext();
const { toast } = useToast();

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import {
Form,
FormControl,
@ -30,6 +29,8 @@ import { useParams } from "next/navigation";
import { Button } from "@app/components/ui/button";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const formSchema = z.object({
email: z.string().email({ message: "Please enter a valid email" }),
@ -40,6 +41,8 @@ export default function AccessControlsPage() {
const { toast } = useToast();
const { orgUser: user } = userOrgUserContext();
const api = createApiClient(useEnvContext());
const { orgId } = useParams();
const [loading, setLoading] = useState(false);

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import { Button } from "@app/components/ui/button";
import {
Form,
@ -39,6 +38,8 @@ import {
import { useOrgContext } from "@app/hooks/useOrgContext";
import { ListRolesResponse } from "@server/routers/role";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
type InviteUserFormProps = {
open: boolean;
@ -55,6 +56,8 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
const { toast } = useToast();
const { org } = useOrgContext();
const api = createApiClient(useEnvContext());
const [inviteLink, setInviteLink] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [expiresInDays, setExpiresInDays] = useState(1);

View file

@ -14,12 +14,13 @@ import { useState } from "react";
import InviteUserForm from "./InviteUserForm";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useUserContext } from "@app/hooks/useUserContext";
import api from "@app/api";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { useToast } from "@app/hooks/useToast";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
export type UserRow = {
id: string;
@ -42,6 +43,8 @@ export default function UsersTable({ users: u }: UsersTableProps) {
const router = useRouter();
const api = createApiClient(useEnvContext());
const user = useUserContext();
const { org } = useOrgContext();
const { toast } = useToast();

View file

@ -1,6 +1,6 @@
"use client";
import api from "@app/api";
import { createApiClient } from "@app/api";
import { Avatar, AvatarFallback } from "@app/components/ui/avatar";
import { Button } from "@app/components/ui/button";
import {
@ -33,6 +33,7 @@ import {
SelectTrigger,
SelectValue,
} from "@app/components/ui/select";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useToast } from "@app/hooks/useToast";
import { cn, formatAxiosError } from "@app/lib/utils";
import { ListOrgsResponse } from "@server/routers/org";
@ -55,6 +56,8 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
const router = useRouter();
const api = createApiClient(useEnvContext());
function getInitials() {
if (name) {
const [firstName, lastName] = name.split(" ");

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import { Button } from "@app/components/ui/button";
import {
Form,
@ -30,6 +29,8 @@ import {
import { formatAxiosError } from "@app/lib/utils";
import { AxiosResponse } from "axios";
import { Resource } from "@server/db/schema";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const setPasswordFormSchema = z.object({
password: z.string().min(4).max(100),
@ -56,6 +57,8 @@ export default function SetResourcePasswordForm({
}: SetPasswordFormProps) {
const { toast } = useToast();
const api = createApiClient(useEnvContext());
const [loading, setLoading] = useState(false);
const form = useForm<SetPasswordFormValues>({

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import { Button } from "@app/components/ui/button";
import {
Form,
@ -35,6 +34,8 @@ import {
InputOTPGroup,
InputOTPSlot,
} from "@app/components/ui/input-otp";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const setPincodeFormSchema = z.object({
pincode: z.string().length(6),
@ -63,6 +64,8 @@ export default function SetResourcePincodeForm({
const [loading, setLoading] = useState(false);
const api = createApiClient(useEnvContext());
const form = useForm<SetPincodeFormValues>({
resolver: zodResolver(setPincodeFormSchema),
defaultValues,

View file

@ -1,7 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import api from "@app/api";
import { ListRolesResponse } from "@server/routers/role";
import { useToast } from "@app/hooks/useToast";
import { useOrgContext } from "@app/hooks/useOrgContext";
@ -36,6 +35,8 @@ import { Binary, Key, ShieldCheck } from "lucide-react";
import SetResourcePasswordForm from "./components/SetResourcePasswordForm";
import { Separator } from "@app/components/ui/separator";
import SetResourcePincodeForm from "./components/SetResourcePincodeForm";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const UsersRolesFormSchema = z.object({
roles: z.array(
@ -58,6 +59,8 @@ export default function ResourceAuthenticationPage() {
const { resource, updateResource, authInfo, updateAuthInfo } =
useResourceContext();
const api = createApiClient(useEnvContext());
const [pageLoading, setPageLoading] = useState(true);
const [allRoles, setAllRoles] = useState<{ id: string; text: string }[]>(

View file

@ -12,7 +12,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import api from "@app/api";
import { AxiosResponse } from "axios";
import { ListTargetsResponse } from "@server/routers/target/listTargets";
import { useForm } from "react-hook-form";
@ -49,9 +48,9 @@ import { useToast } from "@app/hooks/useToast";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { useResourceContext } from "@app/hooks/useResourceContext";
import { ArrayElement } from "@server/types/ArrayElement";
import { Dot } from "lucide-react";
import { formatAxiosError } from "@app/lib/utils";
import { Separator } from "@radix-ui/react-separator";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { createApiClient } from "@app/api";
const addTargetSchema = z.object({
ip: z.string().ip(),
@ -83,6 +82,8 @@ export default function ReverseProxyTargets(props: {
const { toast } = useToast();
const { resource, updateResource } = useResourceContext();
const api = createApiClient(useEnvContext());
const [targets, setTargets] = useState<LocalTarget[]>([]);
const [targetsToRemove, setTargetsToRemove] = useState<number[]>([]);
const [sslEnabled, setSslEnabled] = useState(resource.ssl);

View file

@ -33,7 +33,6 @@ import { useResourceContext } from "@app/hooks/useResourceContext";
import { ListSitesResponse } from "@server/routers/site";
import { useEffect, useState } from "react";
import { AxiosResponse } from "axios";
import api from "@app/api";
import { useParams, useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { GetResourceAuthInfoResponse } from "@server/routers/resource";
@ -43,6 +42,8 @@ import { useOrgContext } from "@app/hooks/useOrgContext";
import CustomDomainInput from "../components/CustomDomainInput";
import ResourceInfoBox from "../components/ResourceInfoBox";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const GeneralFormSchema = z.object({
name: z.string(),
@ -61,6 +62,8 @@ export default function GeneralForm() {
const orgId = params.orgId;
const api = createApiClient(useEnvContext());
const [sites, setSites] = useState<ListSitesResponse["sites"]>([]);
const [saveLoading, setSaveLoading] = useState(false);
const [domainSuffix, setDomainSuffix] = useState(org.org.domain);

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import { Button, buttonVariants } from "@app/components/ui/button";
import {
Form,
@ -9,7 +8,7 @@ import {
FormField,
FormItem,
FormLabel,
FormMessage,
FormMessage
} from "@app/components/ui/form";
import { Input } from "@app/components/ui/input";
import { useToast } from "@app/hooks/useToast";
@ -25,7 +24,7 @@ import {
CredenzaDescription,
CredenzaFooter,
CredenzaHeader,
CredenzaTitle,
CredenzaTitle
} from "@app/components/Credenza";
import { useParams, useRouter } from "next/navigation";
import { ListSitesResponse } from "@server/routers/site";
@ -34,7 +33,7 @@ import { CheckIcon } from "lucide-react";
import {
Popover,
PopoverContent,
PopoverTrigger,
PopoverTrigger
} from "@app/components/ui/popover";
import {
Command,
@ -42,7 +41,7 @@ import {
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandList
} from "@app/components/ui/command";
import { CaretSortIcon } from "@radix-ui/react-icons";
import CustomDomainInput from "../[resourceId]/components/CustomDomainInput";
@ -50,11 +49,13 @@ import { Axios, AxiosResponse } from "axios";
import { Resource } from "@server/db/schema";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { subdomainSchema } from "@server/schemas/subdomainSchema";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const accountFormSchema = z.object({
subdomain: subdomainSchema,
name: z.string(),
siteId: z.number(),
siteId: z.number()
});
type AccountFormValues = z.infer<typeof accountFormSchema>;
@ -66,10 +67,12 @@ type CreateResourceFormProps = {
export default function CreateResourceForm({
open,
setOpen,
setOpen
}: CreateResourceFormProps) {
const { toast } = useToast();
const api = createApiClient(useEnvContext());
const [loading, setLoading] = useState(false);
const params = useParams();
@ -85,8 +88,8 @@ export default function CreateResourceForm({
resolver: zodResolver(accountFormSchema),
defaultValues: {
subdomain: "",
name: "My Resource",
},
name: "My Resource"
}
});
useEffect(() => {
@ -96,7 +99,7 @@ export default function CreateResourceForm({
const fetchSites = async () => {
const res = await api.get<AxiosResponse<ListSitesResponse>>(
`/org/${orgId}/sites/`,
`/org/${orgId}/sites/`
);
setSites(res.data.data.sites);
@ -116,9 +119,9 @@ export default function CreateResourceForm({
`/org/${orgId}/site/${data.siteId}/resource/`,
{
name: data.name,
subdomain: data.subdomain,
subdomain: data.subdomain
// subdomain: data.subdomain,
},
}
)
.catch((e) => {
toast({
@ -126,8 +129,8 @@ export default function CreateResourceForm({
title: "Error creating resource",
description: formatAxiosError(
e,
"An error occurred when creating the resource",
),
"An error occurred when creating the resource"
)
});
});
@ -198,7 +201,7 @@ export default function CreateResourceForm({
onChange={(value) =>
form.setValue(
"subdomain",
value,
value
)
}
/>
@ -227,14 +230,14 @@ export default function CreateResourceForm({
className={cn(
"w-[350px] justify-between",
!field.value &&
"text-muted-foreground",
"text-muted-foreground"
)}
>
{field.value
? sites.find(
(site) =>
site.siteId ===
field.value,
field.value
)?.name
: "Select site"}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
@ -261,7 +264,7 @@ export default function CreateResourceForm({
onSelect={() => {
form.setValue(
"siteId",
site.siteId,
site.siteId
);
}}
>
@ -271,14 +274,14 @@ export default function CreateResourceForm({
site.siteId ===
field.value
? "opacity-100"
: "opacity-0",
: "opacity-0"
)}
/>
{
site.name
}
</CommandItem>
),
)
)}
</CommandGroup>
</CommandList>

View file

@ -21,13 +21,14 @@ import {
} from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import api from "@app/api";
import CreateResourceForm from "./CreateResourceForm";
import { useState } from "react";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { set } from "zod";
import { formatAxiosError } from "@app/lib/utils";
import { useToast } from "@app/hooks/useToast";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
export type ResourceRow = {
id: number;
@ -49,6 +50,8 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
const { toast } = useToast();
const api = createApiClient(useEnvContext());
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedResource, setSelectedResource] =

View file

@ -15,11 +15,12 @@ import {
import { Input } from "@/components/ui/input";
import { useSiteContext } from "@app/hooks/useSiteContext";
import { useForm } from "react-hook-form";
import api from "@app/api";
import { useToast } from "@app/hooks/useToast";
import { useRouter } from "next/navigation";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const GeneralFormSchema = z.object({
name: z.string(),
@ -31,6 +32,8 @@ export default function GeneralPage() {
const { site, updateSite } = useSiteContext();
const { toast } = useToast();
const api = createApiClient(useEnvContext());
const router = useRouter();
const form = useForm<GeneralFormValues>({

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import { Button, buttonVariants } from "@app/components/ui/button";
import {
Form,
@ -41,6 +40,8 @@ import {
SelectValue,
} from "@app/components/ui/select";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const method = [
{ label: "Newt", value: "newt" },
@ -74,6 +75,8 @@ type CreateSiteFormProps = {
export default function CreateSiteForm({ open, setOpen }: CreateSiteFormProps) {
const { toast } = useToast();
const api = createApiClient(useEnvContext());
const [loading, setLoading] = useState(false);
const params = useParams();

View file

@ -12,13 +12,14 @@ import { Button } from "@app/components/ui/button";
import { ArrowRight, ArrowUpDown, MoreHorizontal } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import api from "@app/api";
import { AxiosResponse } from "axios";
import { useState } from "react";
import CreateSiteForm from "./CreateSiteForm";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { useToast } from "@app/hooks/useToast";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
export type SiteRow = {
id: number;
@ -44,6 +45,8 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
const api = createApiClient(useEnvContext());
const callApi = async () => {
const res = await api.put<AxiosResponse<any>>(`/newt`);
console.log(res);

View file

@ -1,6 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import { useEffect, useState, useSyncExternalStore } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
@ -29,7 +29,6 @@ import {
InputOTPGroup,
InputOTPSlot,
} from "@app/components/ui/input-otp";
import api from "@app/api";
import { useRouter } from "next/navigation";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { formatAxiosError } from "@app/lib/utils";
@ -38,6 +37,8 @@ import LoginForm from "@app/components/LoginForm";
import { AuthWithPasswordResponse } from "@server/routers/resource";
import { redirect } from "next/dist/server/api-utils";
import ResourceAccessDenied from "./ResourceAccessDenied";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const pinSchema = z.object({
pin: z
@ -83,6 +84,8 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
const [accessDenied, setAccessDenied] = useState<boolean>(false);
const [loadingLogin, setLoadingLogin] = useState(false);
const api = createApiClient(useEnvContext());
function getDefaultSelectedMethod() {
if (props.methods.sso) {
return "sso";

View file

@ -27,7 +27,6 @@ import {
InputOTPGroup,
InputOTPSlot,
} from "@/components/ui/input-otp";
import api from "@app/api";
import { AxiosResponse } from "axios";
import { VerifyEmailResponse } from "@server/routers/auth";
import { Loader2 } from "lucide-react";
@ -35,6 +34,8 @@ import { Alert, AlertDescription } from "../../../components/ui/alert";
import { useToast } from "@app/hooks/useToast";
import { useRouter } from "next/navigation";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
const FormSchema = z.object({
email: z.string().email({ message: "Invalid email address" }),
@ -61,6 +62,8 @@ export default function VerifyEmailForm({
const { toast } = useToast();
const api = createApiClient(useEnvContext());
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {

View file

@ -6,7 +6,7 @@
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 5.0% 10.0%;
--foreground: 0 0.0% 10.0%;
--card: 0 0% 100%;
--card-foreground: 20 5.0% 10.0%;
--popover: 0 0% 100%;
@ -33,7 +33,7 @@
}
.dark {
--background: 20 5.0% 10.0%;
--background: 0 0.0% 10.0%;
--foreground: 60 9.1% 97.8%;
--card: 20 5.0% 10.0%;
--card-foreground: 60 9.1% 97.8%;

View file

@ -1,14 +1,15 @@
"use client";
import api from "@app/api";
import { createApiClient } from "@app/api";
import { Button } from "@app/components/ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
CardTitle
} from "@app/components/ui/card";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { XCircle } from "lucide-react";
import { useRouter } from "next/navigation";
@ -19,10 +20,12 @@ type InviteStatusCardProps = {
export default function InviteStatusCard({
type,
token,
token
}: InviteStatusCardProps) {
const router = useRouter();
const api = createApiClient(useEnvContext());
async function goToLogin() {
await api.post("/auth/logout", {});
router.push(`/auth/login?redirect=/invite?token=${token}`);

View file

@ -1,23 +1,19 @@
import type { Metadata } from "next";
import "./globals.css";
import { Figtree, IBM_Plex_Sans, Inter, Work_Sans } from "next/font/google";
import { Figtree } from "next/font/google";
import { Toaster } from "@/components/ui/toaster";
import { ThemeProvider } from "@app/providers/ThemeProvider";
import EnvProvider from "@app/providers/EnvProvider";
export const metadata: Metadata = {
title: `Dashboard - Pangolin`,
description: "",
description: ""
};
// const font = Inter({ subsets: ["latin"] });
// const font = Noto_Sans_Mono({ subsets: ["latin"] });
// const font = Work_Sans({ subsets: ["latin"] });
// const font = Space_Grotesk({subsets: ["latin"]})
// const font = IBM_Plex_Sans({subsets: ["latin"], weight: "400"})
const font = Figtree({ subsets: ["latin"] });
export default async function RootLayout({
children,
children
}: Readonly<{
children: React.ReactNode;
}>) {
@ -29,8 +25,19 @@ export default async function RootLayout({
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<EnvProvider
// it's import not to pass all of process.env here in case of secrets
// select only the necessary ones
env={{
NEXT_PORT: process.env.NEXT_PORT as string,
SERVER_EXTERNAL_PORT: process.env
.SERVER_EXTERNAL_PORT as string,
ENVIRONMENT: process.env.ENVIRONMENT as string
}}
>
{children}
</EnvProvider>
<Toaster />
</ThemeProvider>
</body>

View file

@ -4,7 +4,6 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Link from "next/link";
import api from "@app/api";
import { toast } from "@app/hooks/useToast";
import { useCallback, useEffect, useState } from "react";
import {
@ -16,6 +15,8 @@ import {
} from "@app/components/ui/card";
import CopyTextBox from "@app/components/CopyTextBox";
import { formatAxiosError } from "@app/lib/utils";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
type Step = "org" | "site" | "resources";
@ -28,6 +29,8 @@ export default function StepperForm() {
const [orgCreated, setOrgCreated] = useState(false);
const [orgIdTaken, setOrgIdTaken] = useState(false);
const api = createApiClient(useEnvContext());
const checkOrgIdAvailability = useCallback(async (value: string) => {
try {
const res = await api.get(`/org/checkId`, {

View file

@ -1,6 +1,5 @@
"use client";
import api from "@app/api";
import { Button } from "@app/components/ui/button";
import {
Form,
@ -42,6 +41,8 @@ import {
} from "@app/components/Credenza";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { Description } from "@radix-ui/react-toast";
import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
type InviteUserFormProps = {
open: boolean;
@ -64,6 +65,8 @@ export default function InviteUserForm({
}: InviteUserFormProps) {
const [loading, setLoading] = useState(false);
const api = createApiClient(useEnvContext());
const formSchema = z.object({
string: z.string().refine((val) => val === string, {
message: "Invalid confirmation",

View file

@ -0,0 +1,10 @@
import { env } from "@app/lib/types/env";
import { createContext } from "react";
interface EnvContextType {
env: env;
}
const EnvContext = createContext<EnvContextType | undefined>(undefined);
export default EnvContext;

View file

@ -0,0 +1,10 @@
import EnvContext from "@app/contexts/envContext";
import { useContext } from "react";
export function useEnvContext() {
const context = useContext(EnvContext);
if (context === undefined) {
throw new Error("useEnvContext must be used within an EnvProvider");
}
return context;
}

5
src/lib/types/env.ts Normal file
View file

@ -0,0 +1,5 @@
export type env = {
SERVER_EXTERNAL_PORT: string;
NEXT_PORT: string;
ENVIRONMENT: string;
};

View file

@ -0,0 +1,17 @@
"use client";
import EnvContext from "@app/contexts/envContext";
import { env } from "@app/lib/types/env";
interface ApiProviderProps {
children: React.ReactNode;
env: env;
}
export function EnvProvider({ children, env }: ApiProviderProps) {
return (
<EnvContext.Provider value={{ env }}>{children}</EnvContext.Provider>
);
}
export default EnvProvider;