DennisK
DennisK
Explore posts from servers
TTCTheo's Typesafe Cult
Created by DennisK on 9/5/2023 in #questions
Admin routes and security. How to set this up?
Hi! What is a good way of protecting admin routes? The ideal situation is, that whenever an user goes to an admin route it: 1. Does not fetch the data 2. Will get redirected back to a specific page Right now when I throw a TRPC error, it takes a super long time until the error is shown.. It fetches like 7-8 times and returns error = null admin route:
getAllMembers: protectedProcedure.query(async ({ ctx }) => {
const isAdmin = ctx.session.user.role === "admin";
if (!isAdmin) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to view this page",
});
}
const members = // db call

return members;
}),
getAllMembers: protectedProcedure.query(async ({ ctx }) => {
const isAdmin = ctx.session.user.role === "admin";
if (!isAdmin) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to view this page",
});
}
const members = // db call

return members;
}),
const MembersOverview: NextPage = () => {
const router = useRouter();
const { data: sessionData } = useSession();

const {
data: members,
isLoading,
error,
} = api.admin.getAllMembers.useQuery();

// This does not really work
if (error instanceof TRPCClientError) {
if (error.shape.data?.code === "UNAUTHORIZED") {
router.push("/members");
}
}

return (
<>
<DashboardLayout profileData={sessionData?.user}>
<Spacer size="xs" />
{!isLoading && (
// @ts-ignore
<MemberDataTable columns={columns} data={members} />
)}
</DashboardLayout>
</>
);
};
const MembersOverview: NextPage = () => {
const router = useRouter();
const { data: sessionData } = useSession();

const {
data: members,
isLoading,
error,
} = api.admin.getAllMembers.useQuery();

// This does not really work
if (error instanceof TRPCClientError) {
if (error.shape.data?.code === "UNAUTHORIZED") {
router.push("/members");
}
}

return (
<>
<DashboardLayout profileData={sessionData?.user}>
<Spacer size="xs" />
{!isLoading && (
// @ts-ignore
<MemberDataTable columns={columns} data={members} />
)}
</DashboardLayout>
</>
);
};
9 replies
TTCTheo's Typesafe Cult
Created by DennisK on 8/8/2023 in #questions
Vercel builds errorring out due to Invalid Vercel URL.
Hi! Stuck during first deployment of my T3 app. It keeps saying the VERCEL_URL is invalid but looking at the env variables the VERCEL_URL is set and is set to https://**.vercel.app VERCEL_URL: z.string().url().optional(), VERCEL_URL: process.env.VERCEL_URL,
5 replies
TTCTheo's Typesafe Cult
Created by DennisK on 7/21/2023 in #questions
Eslint config is not picked up??!
Hi! I have some rules added to the .eslintrc.cjs inside the T3 app, but any rule I add is not getting picked up by Eslint. Restarting Eslint server etc etc does not work either. Unsafe member access .enabled on an any value.eslint@typescript-eslint/no-unsafe-member-access
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require("path");

/** @type {import("eslint").Linter.Config} */
const config = {
overrides: [
{
extends: [
"plugin:@typescript-eslint/recommended-requiring-type-checking",
],
files: ["*.ts", "*.tsx"],
parserOptions: {
project: path.join(__dirname, "tsconfig.json"),
},
},
],
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "tsconfig.json"),
},
plugins: ["@typescript-eslint"],
extends: ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"],
rules: {
"@typescript-eslint/consistent-type-imports": [
"warn",
{
prefer: "type-imports",
fixStyle: "inline-type-imports",
},
],
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-misused-promises": [
2,
{
checksVoidReturn: {
attributes: false,
},
},
],
"@typescript-eslint/restrict-plus-operands": 0,
"@typescript-eslint/no-unsafe-member-access": 0,
},
};

module.exports = config;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require("path");

/** @type {import("eslint").Linter.Config} */
const config = {
overrides: [
{
extends: [
"plugin:@typescript-eslint/recommended-requiring-type-checking",
],
files: ["*.ts", "*.tsx"],
parserOptions: {
project: path.join(__dirname, "tsconfig.json"),
},
},
],
parser: "@typescript-eslint/parser",
parserOptions: {
project: path.join(__dirname, "tsconfig.json"),
},
plugins: ["@typescript-eslint"],
extends: ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"],
rules: {
"@typescript-eslint/consistent-type-imports": [
"warn",
{
prefer: "type-imports",
fixStyle: "inline-type-imports",
},
],
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-misused-promises": [
2,
{
checksVoidReturn: {
attributes: false,
},
},
],
"@typescript-eslint/restrict-plus-operands": 0,
"@typescript-eslint/no-unsafe-member-access": 0,
},
};

module.exports = config;
3 replies
TTCTheo's Typesafe Cult
Created by DennisK on 7/17/2023 in #questions
Creating (guest)checkout sessions with TRPC/Stripe/Zustand.
Hi! Just asking to see if this is a correct approach I am managing carts using Zustand since it is just a simple side feature of the website where they sell some merchandising. Since customers can order as a guest, I am creating a public createCheckout link function in my stripeRouter. If users have a account I will link the order to the user.
createGuestCheckoutSession: publicProcedure
.input(
z.object({
cart: z.array(
z.object({
id: z.string(),
price: z.number(),
image: z.string(),
quantity: z.number().optional(),
})
),
})
)
.mutation(async ({ ctx, input }) => {
const { stripe, req } = ctx;

const lineItems = input.cart.map((product) => ({
price: product.id,
quantity: product.quantity ?? 1,
}));

const checkoutSession = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
line_items: lineItems,
success_url: `${baseUrl}/shop?checkoutSuccess=true`,
cancel_url: `${baseUrl}/shop?checkoutCanceled=true`,
});

if (!checkoutSession) {
throw new Error("Could not create checkout session");
}

return { checkoutUrl: checkoutSession.url };
}),
});
createGuestCheckoutSession: publicProcedure
.input(
z.object({
cart: z.array(
z.object({
id: z.string(),
price: z.number(),
image: z.string(),
quantity: z.number().optional(),
})
),
})
)
.mutation(async ({ ctx, input }) => {
const { stripe, req } = ctx;

const lineItems = input.cart.map((product) => ({
price: product.id,
quantity: product.quantity ?? 1,
}));

const checkoutSession = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
line_items: lineItems,
success_url: `${baseUrl}/shop?checkoutSuccess=true`,
cancel_url: `${baseUrl}/shop?checkoutCanceled=true`,
});

if (!checkoutSession) {
throw new Error("Could not create checkout session");
}

return { checkoutUrl: checkoutSession.url };
}),
});
And then my checkoutButton creates the checkoutUrl:
const CheckoutButton = () => {
const { mutateAsync: createCheckoutSession } =
api.stripe.createGuestCheckoutSession.useMutation();
const { push } = useRouter();
const cart = useFromStore(useCartStore, (state) => state.cart);

return (
<button
onClick={async () => {
if (!cart) return;
const { checkoutUrl } = await createCheckoutSession({ cart });

if (checkoutUrl) {
void push(checkoutUrl);
}
}}
>
Go to checkout
</button>
);
};
const CheckoutButton = () => {
const { mutateAsync: createCheckoutSession } =
api.stripe.createGuestCheckoutSession.useMutation();
const { push } = useRouter();
const cart = useFromStore(useCartStore, (state) => state.cart);

return (
<button
onClick={async () => {
if (!cart) return;
const { checkoutUrl } = await createCheckoutSession({ cart });

if (checkoutUrl) {
void push(checkoutUrl);
}
}}
>
Go to checkout
</button>
);
};
Is this fine?
2 replies
TTCTheo's Typesafe Cult
Created by DennisK on 7/16/2023 in #questions
Making CartContext available in trpc?
Hi! Simple trpc/t3 noob question. I made a CartContext provider wrapped around my app, and now I would like trpc to create the Stripe checkout url based on the items in the cart. How can I access cart.items? I guess I have to do something in trpc.ts?
<SessionProvider session={session}>
<CartContext.Provider value={cartContext}>
<ErrorBoundary>
<Component {...pageProps} />
</ErrorBoundary>
</CartContext.Provider>
</SessionProvider>
);
};

export default api.withTRPC(MyApp);
<SessionProvider session={session}>
<CartContext.Provider value={cartContext}>
<ErrorBoundary>
<Component {...pageProps} />
</ErrorBoundary>
</CartContext.Provider>
</SessionProvider>
);
};

export default api.withTRPC(MyApp);
stripe router:
createShoppingCheckoutSession: publicProcedure.mutation(async ({ ctx }) => {
const { stripe, req, cart } = ctx;

console.log(cart); // Undefined for now

const line_items = cart.map((item) => ({
price: item.id,
quantity: item.quantity,
}));

const baseUrl =
env.NODE_ENV === "development"
? `http://${req?.headers.host ?? "localhost:3000"}`
: `https://${req?.headers.host ?? env.NEXTAUTH_URL}`;

const checkoutSession = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
line_items,
success_url: `${baseUrl}/thank-you?checkoutSuccess=true`,
cancel_url: `${baseUrl}/thank-you?checkoutCanceled=true`,
});

if (!checkoutSession) {
throw new Error("Could not create checkout session");
}

return { checkoutUrl: checkoutSession.url };
}),
createShoppingCheckoutSession: publicProcedure.mutation(async ({ ctx }) => {
const { stripe, req, cart } = ctx;

console.log(cart); // Undefined for now

const line_items = cart.map((item) => ({
price: item.id,
quantity: item.quantity,
}));

const baseUrl =
env.NODE_ENV === "development"
? `http://${req?.headers.host ?? "localhost:3000"}`
: `https://${req?.headers.host ?? env.NEXTAUTH_URL}`;

const checkoutSession = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
line_items,
success_url: `${baseUrl}/thank-you?checkoutSuccess=true`,
cancel_url: `${baseUrl}/thank-you?checkoutCanceled=true`,
});

if (!checkoutSession) {
throw new Error("Could not create checkout session");
}

return { checkoutUrl: checkoutSession.url };
}),
2 replies
TTCTheo's Typesafe Cult
Created by DennisK on 7/14/2023 in #questions
Handling memberships
Hi! We want to add memberships to our dashboard and hide specific sidebar menu options. What is best practice to handle these kind of things? Make a membership provider around the app? Or could I just call the api in the sidebar component
export function Sidebar({ className }: { className?: string }) {
const pathname = usePathname();
const { data } = api.user.subscriptionStatus.useQuery();
export function Sidebar({ className }: { className?: string }) {
const pathname = usePathname();
const { data } = api.user.subscriptionStatus.useQuery();
5 replies
TTCTheo's Typesafe Cult
Created by DennisK on 7/14/2023 in #questions
Loading state when quering profile data
Hi people! Started with T3 stack and working on the profile page right now. I am trying to get the current logged in profile data and I am using a protected route/api for that. However, how can I make sure the 404 page is not shown when the data is not fully loaded yet?
const Profile: NextPage = ({}) => {
const { data: profile } = api.user.me.useQuery();

if (!profile) return <ErrorPage statusCode={404} />;
console.log(profile);
return (
<>
<Head>
<title>My profile - FirstName</title>
</Head>
<div className="relative flex min-h-screen flex-col">
<div className="flex-1">
<DashboardLayout>
<div className="max-w-5xl">
<div className="max-w-2xl">
<Heading className="uppercase">My profile</Heading>
</div>
<Spacer size="md" />
{/* Account info */}
<section>
<div className="flex w-full justify-between">
<Heading as="h3" size="h3" className="uppercase">
Account info
</Heading>
</div>

<Spacer size="3xs" />
<Separator />
<Spacer size="3xs" />
<div className="grid grid-cols-2 gap-x-4">
<AccountInfo showConfirmChanges={false} />
</div>
</section>
<Spacer size="4xs" />
{/* Personal details */}
const Profile: NextPage = ({}) => {
const { data: profile } = api.user.me.useQuery();

if (!profile) return <ErrorPage statusCode={404} />;
console.log(profile);
return (
<>
<Head>
<title>My profile - FirstName</title>
</Head>
<div className="relative flex min-h-screen flex-col">
<div className="flex-1">
<DashboardLayout>
<div className="max-w-5xl">
<div className="max-w-2xl">
<Heading className="uppercase">My profile</Heading>
</div>
<Spacer size="md" />
{/* Account info */}
<section>
<div className="flex w-full justify-between">
<Heading as="h3" size="h3" className="uppercase">
Account info
</Heading>
</div>

<Spacer size="3xs" />
<Separator />
<Spacer size="3xs" />
<div className="grid grid-cols-2 gap-x-4">
<AccountInfo showConfirmChanges={false} />
</div>
</section>
<Spacer size="4xs" />
{/* Personal details */}
5 replies