Skip to content

Add intellisense and fix side-effects #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -10,9 +10,7 @@
"editor.defaultFormatter": "Prisma.prisma"
},
// Class Variance Authority
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
],
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]],
"workbench.editor.labelFormat": "short",
// Custom file nesting
"explorer.fileNesting.enabled": true,
@@ -25,7 +23,9 @@
},
// Override spellings in workspace
"cSpell.words": [
"AICOPY",
"clsx",
"EXTRACOPY",
"formik",
"headlessui",
"heroicons",
31 changes: 31 additions & 0 deletions migrate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Tier } from "tier";

export const tier = new Tier({
baseURL: process.env.TIER_BASE_URL,
apiKey: process.env.TIER_API_KEY,
debug: true,
});

// Constants
const userId = "xxxxxxx"; // Fetch all users from your DB and loop through them
const featureName = "feature:aicopy"; // The feature where you want to migrate the usage
const newPlan = "plan:free@1"; // Your new plan

const action = async () => {
const limits = await tier.lookupLimits(`org:${userId}`);
const freeFeatureUsage = limits.usage.find((_usage) => _usage.feature === featureName).used;

await tier.subscribe(`org:${userId}`, newPlan);
await tier.report(`org:${userId}`, featureName, freeFeatureUsage);

const updatedLimits = await tier.lookupLimits(`org:${userId}`);
return updatedLimits;
};

action()
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
4 changes: 3 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import "./src/env.mjs";
// We ignore point 4 at https://env.t3.gg/docs/nextjs as env.mjs is now env.js
// env.mjs was renamed to env.js to remove any error caused while importing this to other modules in the project
// import "./src/env.mjs";

/** @type {import('next').NextConfig} */
const nextConfig = {
77 changes: 0 additions & 77 deletions pricing.json

This file was deleted.

28 changes: 13 additions & 15 deletions src/app/(app)/billing/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Metadata } from "next";
import { clsx } from "clsx";
import type Stripe from "stripe";
import type { CurrentPhase, LookupOrgResponse, PaymentMethodsResponse, Usage } from "tier";

import { PricingTableData } from "@/types";
import { TIER_AICOPY_FEATURE_ID } from "@/config/tierConstants";
import { pullCurrentPlan } from "@/lib/services/currentPlan";
import { pullPricingTableData } from "@/lib/services/pricingTableData";
@@ -15,37 +17,33 @@ import { TierLogo } from "@/res/logos/TierLogo";
import { CheckoutButton } from "./CheckoutButton";

export const dynamic = "force-dynamic";
export const revalidate = 0;

export const metadata: Metadata = {
title: "Billing",
description: "Manage your subscription and know about your usage",
};

export default async function BillingPage() {
const pricing = await pullPricingTableData();

const user = await getCurrentUser();

// Fetch the feature consumption and limit of the AI copy feature for the plan currently subscribed
const featureLimits = await tier.lookupLimit(`org:${user?.id}`, TIER_AICOPY_FEATURE_ID);
let [pricing, featureLimits, phase, org, paymentMethodResponse] = await Promise.all([
pullPricingTableData(),
// Fetch the feature consumption and limit of the AI copy feature for the plan currently subscribed
tier.lookupLimit(`org:${user?.id}`, TIER_AICOPY_FEATURE_ID),
// Fetch the phase data of the current subscription
tier.lookupPhase(`org:${user?.id}`),
// Fetch organization/user details
tier.lookupOrg(`org:${user?.id}`),
// Fetch the saved payment methods
tier.lookupPaymentMethods(`org:${user?.id}`),
]);

const usageLimit = featureLimits.limit;
const used = featureLimits.used;

// Fetch the phase data of the current subscription
const phase = await tier.lookupPhase(`org:${user?.id}`);
console.log(phase.current?.end);

// Fetch the current plan from the pricing table data
const currentPlan = await pullCurrentPlan(phase, pricing);

// Fetch organization/user details
const org = await tier.lookupOrg(`org:${user?.id}`);

// Fetch the saved payment methods
const paymentMethodResponse = await tier.lookupPaymentMethods(`org:${user?.id}`);

const paymentMethod = paymentMethodResponse.methods[0] as unknown as Stripe.PaymentMethod;

return (
1 change: 0 additions & 1 deletion src/app/(app)/generate/page.tsx
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ import { tier } from "@/lib/tier";
import { Generate } from "@/components/app/GenerateSection";

export const dynamic = "force-dynamic";
export const revalidate = 0;

export const metadata: Metadata = {
title: "Generate Copy",
1 change: 0 additions & 1 deletion src/app/(app)/history/page.tsx
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import { getCurrentUser } from "@/lib/session";
import { Button } from "@/components/ui/Button";

export const dynamic = "force-dynamic";
export const revalidate = 0;

export const metadata: Metadata = {
title: "History",
15 changes: 10 additions & 5 deletions src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import Link from "next/link";

import { Footer } from "@/components/Footer";
import { Header } from "@/components/app/Header";
import { Footer } from "@/components/Footer";

interface AuthLayoutProps {
children: React.ReactNode;
}

export default function AppLayout({ children }: AuthLayoutProps) {
export default async function AppLayout({ children }: AuthLayoutProps) {
const res = await fetch("https://api.github.com/repos/tierrun/tier-vercel-openai", {
method: "GET",
next: { revalidate: 60 },
});
const data = await res.json();

const stargazers_count: number = data.stargazers_count;
return (
<>
<Header />
<Header stargazers_count={stargazers_count} />
<main className="mx-auto max-w-7xl px-12">
<div className="px-6 lg:px-8">{children}</div>
</main>
11 changes: 10 additions & 1 deletion src/app/(marketing)/layout.tsx
Original file line number Diff line number Diff line change
@@ -8,9 +8,18 @@ interface MarketingLayoutProps {
}

export default async function MarketingLayout({ children }: MarketingLayoutProps) {
const res = await fetch("https://api.github.com/repos/tierrun/tier-vercel-openai", {
method: "GET",
next: { revalidate: 60 },
});
const data = await res.json();

const stargazers_count: number = data.stargazers_count;

console.log(data);
return (
<>
<Header />
<Header stargazers_count={stargazers_count} />
<main>{children}</main>
<Footer />
</>
34 changes: 0 additions & 34 deletions src/app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
@@ -4,46 +4,12 @@ import { BgPattern } from "@/components/ui/Bgpattern";
import { SignUpButton } from "@/components/marketing/LandingSignUp";

export default async function IndexPage() {
const res = await fetch("https://api.github.com/repos/tierrun/tier-vercel-openai", {
method: "GET",
next: { revalidate: 60 },
});
const data = await res.json();

function GitHubIcon(props: any) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z"
/>
</svg>
);
}
return (
<>
{/* Bg Pattern */}
<BgPattern />
{/* Hero Copy */}
<div className="mt-16 flex flex-col items-center gap-4">
<div className="flex">
<Link
href="https://github.com/tierrun/tier-vercel-openai"
className="caption-s flex items-center gap-1 rounded-l-[4px] border border-slate-7 bg-slate-3 px-2 py-[2px] font-medium text-slate-12 hover:border-slate-8 hover:bg-slate-4"
>
<GitHubIcon className="h-5 w-5 fill-slate-12" />
<span className="">Star</span>
</Link>
<Link
href="https://github.com/tierrun/tier-vercel-openai/stargazers"
className="group rounded-r-[4px] border-y border-r border-slate-7 bg-slate-1 px-2 py-[2px] hover:bg-slate-2"
>
<span className="caption-s font-medium text-slate-12 group-hover:text-crimson-9">
{data.stargazers_count}
</span>
</Link>
</div>
<h1 className="md:display h2 w-full px-4 text-center md:w-[802px] md:px-0">
Generate the best <span className="text-crimson-9">marketing copy</span>
</h1>
2 changes: 1 addition & 1 deletion src/app/api/change-plan/route.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import type Stripe from "stripe";
import type { PlanName } from "tier";
import { z } from "zod";

import { env } from "@/env.mjs";
import { env } from "@/env.js";
import { authOptions } from "@/lib/auth";
import { tier } from "@/lib/tier";

10 changes: 5 additions & 5 deletions src/app/api/generate/route.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { NextFetchEvent } from "next/server";
import { OpenAIStream, StreamingTextResponse } from "ai";
import { z } from "zod";

import { env } from "@/env.mjs";
import { env } from "@/env.js";
import { TIER_AICOPY_FEATURE_ID, TIER_EXTRACOPY_FEATURE_ID } from "@/config/tierConstants";
import { openAI } from "@/lib/ai";
import { tier } from "@/lib/tier";
@@ -18,7 +18,7 @@ const inputSchema = z.object({
userId: z.string(),
});

const generateCopyStream = async (input: string, context: NextFetchEvent) => {
const generateCopyStream = async (input: string) => {
const prompt = `You are a marketing expert and a customer approaches you to write a very short and crisp marketing copy for him or her. They want a marketing copy on the topic of \"${input}\".\n\nThis is the short marketing copy you came up with:\n\n`;

const response = await openAI.createCompletion({
@@ -38,15 +38,15 @@ const generateCopyStream = async (input: string, context: NextFetchEvent) => {
return stream;
};

export async function POST(req: Request, context: NextFetchEvent) {
export async function POST(req: Request) {
try {
const json = await req.json();
const body = inputSchema.parse(json);

const tierAnswer = await tier.can(`org:${body.userId}`, TIER_AICOPY_FEATURE_ID);

if (tierAnswer.ok) {
const stream = await generateCopyStream(body.prompt, context);
const stream = await generateCopyStream(body.prompt);

await tierAnswer.report();

@@ -55,7 +55,7 @@ export async function POST(req: Request, context: NextFetchEvent) {
const tierExtraCopyAnswer = await tier.can(`org:${body.userId}`, TIER_EXTRACOPY_FEATURE_ID);

if (tierExtraCopyAnswer.ok) {
const stream = await generateCopyStream(body.prompt, context);
const stream = await generateCopyStream(body.prompt);

await tierExtraCopyAnswer.report();

1 change: 0 additions & 1 deletion src/app/api/subscribe/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { redirect } from "next/navigation";
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import type Stripe from "stripe";
9 changes: 6 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -29,10 +29,13 @@ export const metadata: Metadata = {
},
],
creator: "tierrun",
metadataBase: new URL(siteConfig.url),
alternates: {
canonical: "/",
},
openGraph: {
type: "website",
locale: "en_US",
url: siteConfig.url,
title: siteConfig.name,
description: siteConfig.description,
siteName: siteConfig.name,
@@ -41,7 +44,7 @@ export const metadata: Metadata = {
card: "summary_large_image",
title: siteConfig.name,
description: siteConfig.description,
images: [`${siteConfig.url}/og.jpg`],
images: ["/og.jpg"],
creator: "@tierrun",
},
icons: {
@@ -52,7 +55,7 @@ export const metadata: Metadata = {
manifest: `${siteConfig.url}/favicons/site.webmanifest`,
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
export default async function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${inter.variable} ${dm_sans.variable}`} suppressHydrationWarning>
<head />
8 changes: 6 additions & 2 deletions src/components/app/Header.tsx
Original file line number Diff line number Diff line change
@@ -3,12 +3,15 @@
import { useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
// https://github.com/tailwindlabs/headlessui/issues/1699
// https://github.com/tailwindlabs/headlessui/issues/1419
import { Dialog } from "@headlessui/react";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import { signOut } from "next-auth/react";

import { Button } from "@/components/ui/Button";
import { Stargazer } from "@/components/ui/Stargazer";
import { BlipLogo } from "@/res/logos/BlipLogo";

const navigation = [
@@ -17,7 +20,7 @@ const navigation = [
{ name: "Billing", href: "/billing" },
];

export function Header() {
export function Header({ stargazers_count }: { stargazers_count: number }) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

let pathname = usePathname();
@@ -32,14 +35,15 @@ export function Header() {
className="mx-auto flex max-w-7xl items-center justify-between px-6 py-3 lg:px-8 lg:py-0"
aria-label="Global"
>
<div className="flex lg:flex-1">
<div className="flex items-center gap-4 lg:flex-1 ">
<Link href="/generate" className="-m-1.5 p-1.5">
<span className="sr-only">Blip</span>
<div className="flex gap-2">
<BlipLogo />
<span className="body-semibold">Blip</span>
</div>
</Link>
<Stargazer count={stargazers_count} />
</div>
<div className="flex lg:hidden">
<button
15 changes: 7 additions & 8 deletions src/components/marketing/Header.tsx
Original file line number Diff line number Diff line change
@@ -3,10 +3,13 @@
import { useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
// https://github.com/tailwindlabs/headlessui/issues/1699
// https://github.com/tailwindlabs/headlessui/issues/1419
import { Dialog } from "@headlessui/react";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";

import { Stargazer } from "@/components/ui/Stargazer";
import { SignInButton } from "@/components/marketing/LandingSignIn";
import { SignUpButton } from "@/components/marketing/LandingSignUp";
import { BlipLogo } from "@/res/logos/BlipLogo";
@@ -16,7 +19,7 @@ const navigation = [
{ name: "Pricing", href: "/pricing" },
];

export function Header() {
export function Header({ stargazers_count }: { stargazers_count: number }) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

let pathname = usePathname();
@@ -27,14 +30,15 @@ export function Header() {
className="mx-auto flex max-w-7xl items-center justify-between px-6 lg:px-8"
aria-label="Global"
>
<div className="flex lg:flex-1">
<div className="flex items-center gap-4 lg:flex-1">
<Link href="/" className="-m-1.5 p-1.5">
<span className="sr-only">Blip</span>
<div className="flex gap-2">
<BlipLogo />
<span className="body-semibold">Blip</span>
</div>
</Link>
<Stargazer count={stargazers_count} />
</div>
<div className="flex lg:hidden">
<button
@@ -64,12 +68,7 @@ export function Header() {
<SignInButton className="block" />
</div>
</nav>
<Dialog
as="div"
className="lg:hidden"
open={mobileMenuOpen}
onClose={setMobileMenuOpen}
>
<Dialog as="div" className="lg:hidden" open={mobileMenuOpen} onClose={setMobileMenuOpen}>
<div className="fixed inset-0 z-10" />
<Dialog.Panel className="fixed inset-y-0 right-0 z-10 w-full overflow-y-auto bg-slate-1 p-6 sm:max-w-sm sm:ring-1 sm:ring-slate-6">
<div className="flex items-center justify-between">
35 changes: 35 additions & 0 deletions src/components/ui/Stargazer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Link from "next/link";

function GitHubIcon(props: any) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z"
/>
</svg>
);
}

export const Stargazer = ({ count }: { count: number }) => {
return (
<div className="flex">
<Link
href="https://github.com/tierrun/tier-vercel-openai"
className="caption-s flex items-center gap-1 rounded-l-[4px] border border-slate-7 bg-slate-3 px-2 py-[2px] font-medium text-slate-12 hover:border-slate-8 hover:bg-slate-4"
>
<GitHubIcon className="h-5 w-5 fill-slate-12" />
<span className="">Star</span>
</Link>
<Link
href="https://github.com/tierrun/tier-vercel-openai/stargazers"
className="group inline-flex items-center rounded-r-[4px] border-y border-r border-slate-7 bg-slate-1 px-2 py-[2px] hover:bg-slate-2"
>
<span className="caption-s font-medium text-slate-12 group-hover:text-crimson-9">
{count}
</span>
</Link>
</div>
);
};
2 changes: 1 addition & 1 deletion src/config/site.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SiteConfig } from "@/types";
import { env } from "@/env.mjs";
import { env } from "@/env.js";

export const siteConfig: SiteConfig = {
name: "Blip",
File renamed without changes.
2 changes: 1 addition & 1 deletion src/lib/ai.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Configuration, OpenAIApi } from "openai-edge";

import { env } from "@/env.mjs";
import { env } from "@/env.js";

const config = new Configuration({
apiKey: env.OPENAI_API_KEY,
2 changes: 1 addition & 1 deletion src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { NextAuthOptions } from "next-auth";
import GithubProvider from "next-auth/providers/github";
import { OrgInfo } from "tier";

import { env } from "@/env.mjs";
import { env } from "@/env.js";
import { TIER_AICOPY_FEATURE_ID, TIER_FREE_PLAN_ID } from "@/config/tierConstants";
import { db } from "@/lib/db";
import { tier } from "@/lib/tier";
2 changes: 1 addition & 1 deletion src/lib/tier.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

import { Tier } from "tier/client"; // If you want to make Tier work on edge

import { env } from "@/env.mjs";
import { env } from "@/env.js";

export const tier = new Tier({
baseURL: env.TIER_BASE_URL as string,
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
"noEmit": true,
"esModuleInterop": true,
"module": "ESNext",
"moduleResolution": "Node",
"moduleResolution": "NodeNext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",