Skip to content

Instantly share code, notes, and snippets.

@Vetrivel-VP
Last active April 1, 2024 05:46
Show Gist options
  • Save Vetrivel-VP/a7803530b94642a9b6335b57a601dc60 to your computer and use it in GitHub Desktop.
Save Vetrivel-VP/a7803530b94642a9b6335b57a601dc60 to your computer and use it in GitHub Desktop.
Youtube Spotify Clone
STRIPE env Variables
----------------------------
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
----------------------------
Stripe Helpers
----------------------------
import { Price } from "@/types";
export const getURL = () => {
let url =
process.env.NEXT_PUBLIC_SITE_URL ??
process.env.NEXT_PUBLIC_VERCEL_URL ??
"http://localhost:3000/";
url = url.includes("http") ? url : `https://${url}`;
url = url.charAt(url.length - 1) === "/" ? url : `${url}/`;
return url;
};
export const postData = async ({
url,
data,
}: {
url: string;
data?: { price: Price };
}) => {
console.log("POST REQUEST:", url, data);
const res: Response = await fetch(url, {
method: "POST",
headers: new Headers({ "Content-Type": "application/json" }),
credentials: "same-origin",
body: JSON.stringify(data),
});
if (!res.ok) {
console.log("Error in POST : ", { url, data, res });
throw new Error(res.statusText);
}
return res.json();
};
export const toDateTime = (secs: number) => {
var t = new Date("1970-01-01T00:30:00Z");
t.setSeconds(secs);
return t;
};
----------------------------
stripe.ts
----------------------------
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? "", {
apiVersion: "2023-10-16",
appInfo: {
name: "Spotify Clone YT Video",
version: "0.1.0",
},
});
----------------------------
stripeClient.ts
----------------------------
import { loadStripe, Stripe } from "@stripe/stripe-js";
let stripePromise: Promise<Stripe | null>;
export const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ?? ""
);
}
return stripePromise;
};
----------------------------
stripe supbaseAdmin.ts
----------------------------
import Stripe from "stripe";
import { createClient } from "@supabase/supabase-js";
import { Database } from "@/types_db";
import { Price, Product } from "@/types";
import { stripe } from "./stripe";
import { toDateTime } from "./helpers";
export const supabaseAdmin = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL || "",
process.env.SUPABASE_SERVICE_ROLE_KEY || ""
);
// upsert product record
const upserProductRecord = async (product: Stripe.Product) => {
const productData: Product = {
id: product.id,
active: product.active,
name: product.name,
description: product.description ?? undefined,
image: product.images?.[0] ?? null,
metadata: product.metadata,
};
const { error } = await supabaseAdmin.from("products").upsert([productData]);
if (error) {
throw error;
}
console.log("Product inserted/updated : ", product.id);
};
const upsertPriceRecord = async (price: Stripe.Price) => {
const priceData: Price = {
id: price.id,
product_id: typeof price.product === "string" ? price.product : "",
active: price.active,
currency: price.currency,
description: price.nickname ?? undefined,
type: price.type,
unit_amount: price.unit_amount ?? undefined,
interval: price.recurring?.interval,
interval_count: price.recurring?.interval_count,
trial_period_days: price.recurring?.trial_period_days,
metadata: price.metadata,
};
const { error } = await supabaseAdmin.from("prices").upsert([priceData]);
if (error) {
throw error;
}
console.log(`Price inserted/updated : ${price.id}`);
};
const createOrRetrieveCustomer = async ({
email,
uuid,
}: {
email: string;
uuid: string;
}) => {
const { data, error } = await supabaseAdmin
.from("customers")
.select("stripe_customer_id")
.eq("id", uuid)
.single();
if (error || !data?.stripe_customer_id) {
const customerData: { metadata: { supabaseUUID: string }; email?: string } =
{
metadata: {
supabaseUUID: uuid,
},
};
if (email) customerData.email = email;
const customer = await stripe.customers.create(customerData);
const { error: supabaseError } = await supabaseAdmin
.from("customers")
.insert([{ id: uuid, stripe_customer_id: customer.id }]);
if (supabaseError) {
throw supabaseError;
}
console.log("New Customer Created:", uuid);
return customer.id;
}
// if customer already on plan
return data.stripe_customer_id;
};
const copyBillingDetailsToCustomer = async (
uuid: string,
payment_method: Stripe.PaymentMethod
) => {
const customer = payment_method.customer as string;
const { name, phone, address } = payment_method.billing_details;
if (!name || !phone || !address) return;
// @ts-ignore
await stripe.customers.update(customer, { name, phone, address });
const { error } = await supabaseAdmin
.from("users")
.update({
billing_address: { ...address },
payment_method: { ...payment_method[payment_method.type] },
})
.eq("id", uuid);
if (error) throw error;
};
const manageSubscriptionStatusChange = async (
subscriptionId: string,
customerId: string,
createAction = false
) => {
const { data: customerData, error: noCustomerError } = await supabaseAdmin
.from("customers")
.select("id")
.eq("stripe_customer_id", customerId)
.single();
if (noCustomerError) throw noCustomerError;
const { id: uuid } = customerData;
const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
expand: ["default_payment_method"],
});
const subscriptionData: Database["public"]["Tables"]["subscriptions"]["Insert"] =
{
id: subscription.id,
user_id: uuid,
metadata: subscription.metadata,
// @ts-ignore
status: subscription.status,
price_id: subscription.items.data[0].price.id,
// @ts-ignore
quantity: subscription.quantity,
cancel_at_period_end: subscription.cancel_at_period_end,
cancel_at: subscription.cancel_at
? toDateTime(subscription.cancel_at).toISOString()
: null,
canceled_at: subscription.canceled_at
? toDateTime(subscription.canceled_at).toISOString()
: null,
current_period_start: toDateTime(
subscription.current_period_start
).toISOString(),
current_period_end: toDateTime(
subscription.current_period_end
).toISOString(),
created: toDateTime(subscription.created).toISOString(),
ended_at: subscription.ended_at
? toDateTime(subscription.ended_at).toISOString()
: null,
trial_start: subscription.trial_start
? toDateTime(subscription.trial_start).toISOString()
: null,
trial_end: subscription.trial_end
? toDateTime(subscription.trial_end).toISOString()
: null,
};
const { error } = await supabaseAdmin
.from("subscriptions")
.upsert([subscriptionData]);
if (error) throw error;
console.log(
`Inserted/ updated subscription [${subscription.id} for ${uuid}]`
);
if (createAction && subscription.default_payment_method && uuid) {
await copyBillingDetailsToCustomer(
uuid,
subscription.default_payment_method as Stripe.PaymentMethod
);
}
};
export {
upserProductRecord,
upsertPriceRecord,
createOrRetrieveCustomer,
manageSubscriptionStatusChange,
};
----------------------------
----------------------------
webhook route.ts
import Stripe from "stripe";
import { NextResponse } from "next/server";
import { headers } from "next/headers";
import { stripe } from "@/libs/stripe";
import {
upserProductRecord,
upsertPriceRecord,
manageSubscriptionStatusChange,
} from "@/libs/supabaseAdmin";
const relevantEvents = new Set([
"product.created",
"product.updated",
"price.created",
"price.updated",
"checkout.session.completed",
"customer.subscription.created",
"customer.subscription.updated",
"customer.subscription.deleted",
]);
export const POST = async (request: Request) => {
const body = await request.text();
const sig = headers().get("Stripe-Signature");
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
let event: Stripe.Event;
try {
if (!sig || !webhookSecret) return;
event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
} catch (error: any) {
console.log(`Error Message : ${error.message}`);
return new NextResponse(`Webhook Error : ${error.message}`, {
status: 400,
});
}
if (relevantEvents.has(event.type)) {
try {
switch (event.type) {
case "product.created":
case "product.updated":
await upserProductRecord(event.data.object as Stripe.Product);
break;
case "price.created":
case "price.updated":
await upsertPriceRecord(event.data.object as Stripe.Price);
break;
case "customer.subscription.created":
case "customer.subscription.updated":
case "customer.subscription.deleted":
const subscription = event.data.object as Stripe.Subscription;
await manageSubscriptionStatusChange(
subscription.id,
subscription.customer as string,
event.type === "customer.subscription.created"
);
break;
case "checkout.session.completed":
const checkoutSession = event.data.object as Stripe.Checkout.Session;
if (checkoutSession.mode === "subscription") {
const subscriptionId = checkoutSession.subscription;
await manageSubscriptionStatusChange(
subscriptionId as string,
checkoutSession.customer as string,
true
);
}
break;
default:
throw new Error("Unhandled Relevant event");
}
} catch (error) {
console.log(error);
return new NextResponse(`Webhook error`, { status: 400 });
}
}
return NextResponse.json({ received: true }, { status: 200 });
};
----------------------------
----------------------------
create-checkout-session
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import { stripe } from "@/libs/stripe";
import { getURL } from "@/libs/helpers";
import { createOrRetrieveCustomer } from "@/libs/supabaseAdmin";
export const POST = async (request: Request) => {
const { price, quantity = 1, metadata = {} } = await request.json();
try {
const supabase = createRouteHandlerClient({
cookies,
});
const {
data: { user },
} = await supabase.auth.getUser();
const customer = await createOrRetrieveCustomer({
uuid: user?.id || "",
email: user?.email || "",
});
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
billing_address_collection: "required",
customer,
line_items: [
{
price: price.id,
quantity,
},
],
mode: "subscription",
allow_promotion_codes: true,
subscription_data: {
metadata,
},
success_url: `${getURL()}/account`,
cancel_url: `${getURL()}/`,
});
return NextResponse.json({ sessionId: session.id });
} catch (err: any) {
console.log(err);
return new NextResponse("Internal Error", { status: 500 });
}
};
----------------------------
----------------------------
create-portal-link
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import { stripe } from "@/libs/stripe";
import { getURL } from "@/libs/helpers";
import { createOrRetrieveCustomer } from "@/libs/supabaseAdmin";
export const POST = async () => {
try {
const supabase = createRouteHandlerClient({ cookies });
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) throw new Error("Could not get user");
const customer = await createOrRetrieveCustomer({
uuid: user?.id || "",
email: user?.email || "",
});
if (!customer) throw new Error("Could not get customer");
const { url } = await stripe.billingPortal.sessions.create({
customer,
return_url: `${getURL()}/account`,
});
return NextResponse.json({ url });
} catch (error) {
console.log(error);
return new NextResponse("Internal Error", { status: 500 });
}
};
----------------------------
types_db.ts
----------------------------
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[]
export type Database = {
public: {
Tables: {
artists: {
Row: {
author: string | null
created_at: string
description: string | null
facebook: string | null
followers: number | null
id: number
instagram: string | null
linkedin: string | null
picture: string | null
twitter: string | null
}
Insert: {
author?: string | null
created_at?: string
description?: string | null
facebook?: string | null
followers?: number | null
id?: number
instagram?: string | null
linkedin?: string | null
picture?: string | null
twitter?: string | null
}
Update: {
author?: string | null
created_at?: string
description?: string | null
facebook?: string | null
followers?: number | null
id?: number
instagram?: string | null
linkedin?: string | null
picture?: string | null
twitter?: string | null
}
Relationships: []
}
customers: {
Row: {
id: string
stripe_customer_id: string | null
}
Insert: {
id: string
stripe_customer_id?: string | null
}
Update: {
id?: string
stripe_customer_id?: string | null
}
Relationships: [
{
foreignKeyName: "customers_id_fkey"
columns: ["id"]
isOneToOne: true
referencedRelation: "users"
referencedColumns: ["id"]
},
]
}
favourites: {
Row: {
created_at: string
song_id: number
user_id: string
}
Insert: {
created_at?: string
song_id: number
user_id: string
}
Update: {
created_at?: string
song_id?: number
user_id?: string
}
Relationships: [
{
foreignKeyName: "public_favourites_song_id_fkey"
columns: ["song_id"]
isOneToOne: false
referencedRelation: "songs"
referencedColumns: ["id"]
},
{
foreignKeyName: "public_favourites_user_id_fkey"
columns: ["user_id"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
]
}
playlists: {
Row: {
created_at: string
id: number
title: string | null
user_id: string | null
}
Insert: {
created_at?: string
id?: number
title?: string | null
user_id?: string | null
}
Update: {
created_at?: string
id?: number
title?: string | null
user_id?: string | null
}
Relationships: [
{
foreignKeyName: "public_playlists_user_id_fkey"
columns: ["user_id"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
]
}
prices: {
Row: {
active: boolean | null
currency: string | null
description: string | null
id: string
interval: Database["public"]["Enums"]["pricing_plan_interval"] | null
interval_count: number | null
metadata: Json | null
product_id: string | null
trial_period_days: number | null
type: Database["public"]["Enums"]["pricing_type"] | null
unit_amount: number | null
}
Insert: {
active?: boolean | null
currency?: string | null
description?: string | null
id: string
interval?: Database["public"]["Enums"]["pricing_plan_interval"] | null
interval_count?: number | null
metadata?: Json | null
product_id?: string | null
trial_period_days?: number | null
type?: Database["public"]["Enums"]["pricing_type"] | null
unit_amount?: number | null
}
Update: {
active?: boolean | null
currency?: string | null
description?: string | null
id?: string
interval?: Database["public"]["Enums"]["pricing_plan_interval"] | null
interval_count?: number | null
metadata?: Json | null
product_id?: string | null
trial_period_days?: number | null
type?: Database["public"]["Enums"]["pricing_type"] | null
unit_amount?: number | null
}
Relationships: [
{
foreignKeyName: "prices_product_id_fkey"
columns: ["product_id"]
isOneToOne: false
referencedRelation: "products"
referencedColumns: ["id"]
},
]
}
products: {
Row: {
active: boolean | null
description: string | null
id: string
image: string | null
metadata: Json | null
name: string | null
}
Insert: {
active?: boolean | null
description?: string | null
id: string
image?: string | null
metadata?: Json | null
name?: string | null
}
Update: {
active?: boolean | null
description?: string | null
id?: string
image?: string | null
metadata?: Json | null
name?: string | null
}
Relationships: []
}
songs: {
Row: {
artist_id: number | null
created_at: string
id: number
image_uri: string | null
song_uri: string | null
title: string | null
user_id: string | null
}
Insert: {
artist_id?: number | null
created_at?: string
id?: number
image_uri?: string | null
song_uri?: string | null
title?: string | null
user_id?: string | null
}
Update: {
artist_id?: number | null
created_at?: string
id?: number
image_uri?: string | null
song_uri?: string | null
title?: string | null
user_id?: string | null
}
Relationships: [
{
foreignKeyName: "public_songs_artist_id_fkey"
columns: ["artist_id"]
isOneToOne: false
referencedRelation: "artists"
referencedColumns: ["id"]
},
{
foreignKeyName: "public_songs_user_id_fkey"
columns: ["user_id"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
]
}
subscriptions: {
Row: {
cancel_at: string | null
cancel_at_period_end: boolean | null
canceled_at: string | null
created: string
current_period_end: string
current_period_start: string
ended_at: string | null
id: string
metadata: Json | null
price_id: string | null
quantity: number | null
status: Database["public"]["Enums"]["subscription_status"] | null
trial_end: string | null
trial_start: string | null
user_id: string
}
Insert: {
cancel_at?: string | null
cancel_at_period_end?: boolean | null
canceled_at?: string | null
created?: string
current_period_end?: string
current_period_start?: string
ended_at?: string | null
id: string
metadata?: Json | null
price_id?: string | null
quantity?: number | null
status?: Database["public"]["Enums"]["subscription_status"] | null
trial_end?: string | null
trial_start?: string | null
user_id: string
}
Update: {
cancel_at?: string | null
cancel_at_period_end?: boolean | null
canceled_at?: string | null
created?: string
current_period_end?: string
current_period_start?: string
ended_at?: string | null
id?: string
metadata?: Json | null
price_id?: string | null
quantity?: number | null
status?: Database["public"]["Enums"]["subscription_status"] | null
trial_end?: string | null
trial_start?: string | null
user_id?: string
}
Relationships: [
{
foreignKeyName: "subscriptions_price_id_fkey"
columns: ["price_id"]
isOneToOne: false
referencedRelation: "prices"
referencedColumns: ["id"]
},
{
foreignKeyName: "subscriptions_user_id_fkey"
columns: ["user_id"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
]
}
users: {
Row: {
avatar_url: string | null
billing_address: Json | null
full_name: string | null
id: string
payment_method: Json | null
}
Insert: {
avatar_url?: string | null
billing_address?: Json | null
full_name?: string | null
id: string
payment_method?: Json | null
}
Update: {
avatar_url?: string | null
billing_address?: Json | null
full_name?: string | null
id?: string
payment_method?: Json | null
}
Relationships: [
{
foreignKeyName: "users_id_fkey"
columns: ["id"]
isOneToOne: true
referencedRelation: "users"
referencedColumns: ["id"]
},
]
}
}
Views: {
[_ in never]: never
}
Functions: {
[_ in never]: never
}
Enums: {
pricing_plan_interval: "day" | "week" | "month" | "year"
pricing_type: "one_time" | "recurring"
subscription_status:
| "trialing"
| "active"
| "canceled"
| "incomplete"
| "incomplete_expired"
| "past_due"
| "unpaid"
}
CompositeTypes: {
[_ in never]: never
}
}
}
type PublicSchema = Database[Extract<keyof Database, "public">]
export type Tables<
PublicTableNameOrOptions extends
| keyof (PublicSchema["Tables"] & PublicSchema["Views"])
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
Database[PublicTableNameOrOptions["schema"]]["Views"])
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
Row: infer R
}
? R
: never
: PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] &
PublicSchema["Views"])
? (PublicSchema["Tables"] &
PublicSchema["Views"])[PublicTableNameOrOptions] extends {
Row: infer R
}
? R
: never
: never
export type TablesInsert<
PublicTableNameOrOptions extends
| keyof PublicSchema["Tables"]
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Insert: infer I
}
? I
: never
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
Insert: infer I
}
? I
: never
: never
export type TablesUpdate<
PublicTableNameOrOptions extends
| keyof PublicSchema["Tables"]
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Update: infer U
}
? U
: never
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
Update: infer U
}
? U
: never
: never
export type Enums<
PublicEnumNameOrOptions extends
| keyof PublicSchema["Enums"]
| { schema: keyof Database },
EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
: never = never,
> = PublicEnumNameOrOptions extends { schema: keyof Database }
? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
: PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
? PublicSchema["Enums"][PublicEnumNameOrOptions]
: never
----------------------------
types.ts
-----------------------------
import Stripe from "stripe";
export interface UserDetails {
id: string;
first_name: string;
last_name: string;
full_name?: string;
avatar_url?: string;
billing_address?: Stripe.Address;
payment_method?: Stripe.PaymentMethod[Stripe.PaymentMethod.Type];
}
export interface Artist {
id: string;
author: string;
picture: string;
description: string;
followers: number;
facebook: string;
instagram: string;
linkedin: string;
}
export interface Song {
id: string;
title: string;
song_uri: string;
image_uri: string;
user_id: string;
artist_id: string;
}
export interface Product {
id: string;
active?: boolean;
name?: string;
description?: string;
image?: string;
metadata?: Stripe.Metadata;
}
export interface Price {
id: string;
product_id?: string;
active?: boolean;
description?: string;
unit_amount?: number;
currency?: string;
type?: Stripe.Price.Type;
interval?: Stripe.Price.Recurring.Interval;
interval_count?: number;
trial_period_days?: number | null;
metadata?: Stripe.Metadata;
products?: Product;
}
export interface ProductWithPrice extends Product {
prices?: Price[];
}
export interface Subscription {
id: string;
user_id: string;
status?: Stripe.Subscription.Status;
metadata?: Stripe.Metadata;
price_id?: string;
quantity?: number;
cancel_at_period_end?: boolean;
created: string;
current_period_start: string;
current_period_end: string;
ended_at?: string;
cancel_at?: string;
canceled_at?: string;
trial_start?: string;
trail_end?: string;
prices?: Price;
}
-----------------------------
useUser.tsx
-----------------------------
import { createContext, useContext, useEffect, useState } from "react";
import { User } from "@supabase/auth-helpers-nextjs";
import {
useSessionContext,
useUser as useSupaUser,
} from "@supabase/auth-helpers-react";
import { Subscription, UserDetails } from "@/types";
type UserContextType = {
accessToken: string | null;
user: User | null;
userDetails: UserDetails | null;
isLoading: boolean;
subscription: Subscription | null;
};
export const UserContext = createContext<UserContextType | undefined>(
undefined
);
export interface Props {
// like it can be general prop name
[propName: string]: any;
}
export const MyUserContextProvider = (props: Props) => {
const {
session,
isLoading: isUserLoading,
supabaseClient: supabase,
} = useSessionContext();
const user = useSupaUser();
const accessToken = session?.access_token ?? null;
const [isLoadingData, setIsLoadingData] = useState(false);
const [userDetails, setUserDetails] = useState<UserDetails | null>(null);
const [subscription, setSubscription] = useState<Subscription | null>(null);
// function to fetch the user detail
const getUserDetails = () => supabase.from("users").select("*").single();
// funtion to fetch the subscription details
const getSubscription = () =>
supabase
.from("subscriptions")
.select("*, prices(*, products(*))")
.in("status", ["trialing", "active"])
.single();
useEffect(() => {
if (user && !isLoadingData && !userDetails && !subscription) {
setIsLoadingData(true);
Promise.allSettled([getUserDetails(), getSubscription()]).then(
(results) => {
const userDetailsPromise = results[0];
const subscriptionDetailPromise = results[1];
if (userDetailsPromise.status === "fulfilled") {
setUserDetails(userDetailsPromise.value.data as UserDetails);
}
if (subscriptionDetailPromise.status === "fulfilled") {
setSubscription(
subscriptionDetailPromise.value.data as Subscription
);
}
setIsLoadingData(false);
}
);
} else if (!user && !isLoadingData && !isUserLoading) {
setUserDetails(null);
setSubscription(null);
}
}, [user, isUserLoading]);
const value = {
accessToken,
user,
userDetails,
isLoading: isUserLoading || isLoadingData,
subscription,
};
return <UserContext.Provider value={value} {...props} />;
};
export const userUser = () => {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error("useUser must be used within a MyUserContextProvider");
}
return context;
};
-----------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment