update pay function
This commit is contained in:
44
boilerplate-chakra-pro-main/utils/auth-helpers/client.ts
Normal file
44
boilerplate-chakra-pro-main/utils/auth-helpers/client.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { createClient } from '@/utils/supabase/client';
|
||||
import { type Provider } from '@supabase/supabase-js';
|
||||
import { getURL } from '@/utils/helpers';
|
||||
import { redirectToPath } from './server';
|
||||
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
|
||||
|
||||
export async function handleRequest(
|
||||
e: React.FormEvent<HTMLFormElement>,
|
||||
requestFunc: (formData: FormData) => Promise<string>,
|
||||
router: AppRouterInstance | null = null
|
||||
): Promise<boolean | void> {
|
||||
// Prevent default form submission refresh
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const redirectUrl: string = await requestFunc(formData);
|
||||
|
||||
if (router) {
|
||||
// If client-side router is provided, use it to redirect
|
||||
return router.push(redirectUrl);
|
||||
} else {
|
||||
// Otherwise, redirect server-side
|
||||
return await redirectToPath(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
export async function signInWithOAuth(e: React.FormEvent<HTMLFormElement>) {
|
||||
// Prevent default form submission refresh
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const provider = String(formData.get('provider')).trim() as Provider;
|
||||
|
||||
// Create client-side supabase client and call signInWithOAuth
|
||||
const supabase = createClient();
|
||||
const redirectURL = getURL('/auth/callback');
|
||||
await supabase.auth.signInWithOAuth({
|
||||
provider: provider,
|
||||
options: {
|
||||
redirectTo: redirectURL
|
||||
}
|
||||
});
|
||||
}
|
||||
340
boilerplate-chakra-pro-main/utils/auth-helpers/server.ts
Normal file
340
boilerplate-chakra-pro-main/utils/auth-helpers/server.ts
Normal file
@@ -0,0 +1,340 @@
|
||||
'use server';
|
||||
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { getURL, getErrorRedirect, getStatusRedirect } from '@/utils/helpers';
|
||||
import { getAuthTypes } from '@/utils/auth-helpers/settings';
|
||||
|
||||
function isValidEmail(email: string) {
|
||||
var regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
|
||||
return regex.test(email);
|
||||
}
|
||||
|
||||
export async function redirectToPath(path: string) {
|
||||
return redirect(path);
|
||||
}
|
||||
|
||||
export async function SignOut(formData: FormData) {
|
||||
const pathName = String(formData.get('pathName')).trim();
|
||||
|
||||
const supabase = createClient();
|
||||
const { error } = await supabase.auth.signOut();
|
||||
|
||||
if (error) {
|
||||
return getErrorRedirect(
|
||||
pathName,
|
||||
'Hmm... Something went wrong.',
|
||||
'You could not be signed out.'
|
||||
);
|
||||
}
|
||||
|
||||
return '/dashboard/signin';
|
||||
}
|
||||
|
||||
export async function signInWithEmail(formData: FormData) {
|
||||
const cookieStore = cookies();
|
||||
const callbackURL = getURL('/auth/callback');
|
||||
|
||||
const email = String(formData.get('email')).trim();
|
||||
let redirectPath: string;
|
||||
|
||||
if (!isValidEmail(email)) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/email_signin',
|
||||
'Invalid email address.',
|
||||
'Please try again.'
|
||||
);
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
let options = {
|
||||
emailRedirectTo: callbackURL,
|
||||
shouldCreateUser: true
|
||||
};
|
||||
|
||||
// If allowPassword is false, do not create a new user
|
||||
const { allowPassword } = getAuthTypes();
|
||||
if (allowPassword) options.shouldCreateUser = false;
|
||||
const { data, error } = await supabase.auth.signInWithOtp({
|
||||
email,
|
||||
options: options
|
||||
});
|
||||
|
||||
if (error) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/email_signin',
|
||||
'You could not be signed in.',
|
||||
error.message
|
||||
);
|
||||
} else if (data) {
|
||||
cookieStore.set('preferredSignInView', 'email_signin', { path: '/' });
|
||||
redirectPath = getStatusRedirect(
|
||||
'/dashboard/signin/email_signin',
|
||||
'Success!',
|
||||
'Please check your email for a magic link. You may now close this tab.',
|
||||
true
|
||||
);
|
||||
} else {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/email_signin',
|
||||
'Hmm... Something went wrong.',
|
||||
'You could not be signed in.'
|
||||
);
|
||||
}
|
||||
|
||||
return redirectPath;
|
||||
}
|
||||
|
||||
export async function requestPasswordUpdate(formData: FormData) {
|
||||
const callbackURL = getURL('/auth/reset_password');
|
||||
|
||||
// Get form data
|
||||
const email = String(formData.get('email')).trim();
|
||||
let redirectPath: string;
|
||||
|
||||
if (!isValidEmail(email)) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/forgot_password',
|
||||
'Invalid email address.',
|
||||
'Please try again.'
|
||||
);
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
|
||||
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: callbackURL
|
||||
});
|
||||
|
||||
if (error) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/forgot_password',
|
||||
error.message,
|
||||
'Please try again.'
|
||||
);
|
||||
} else if (data) {
|
||||
redirectPath = getStatusRedirect(
|
||||
'/dashboard/signin/forgot_password',
|
||||
'Success!',
|
||||
'Please check your email for a password reset link. You may now close this tab.',
|
||||
true
|
||||
);
|
||||
} else {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/forgot_password',
|
||||
'Hmm... Something went wrong.',
|
||||
'Password reset email could not be sent.'
|
||||
);
|
||||
}
|
||||
|
||||
return redirectPath;
|
||||
}
|
||||
|
||||
export async function signInWithPassword(formData: FormData) {
|
||||
const cookieStore = cookies();
|
||||
const email = String(formData.get('email')).trim();
|
||||
const password = String(formData.get('password')).trim();
|
||||
let redirectPath: string;
|
||||
|
||||
const supabase = createClient();
|
||||
const { error, data } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
if (error) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/password_signin',
|
||||
'Sign in failed.',
|
||||
error.message
|
||||
);
|
||||
} else if (data.user) {
|
||||
cookieStore.set('preferredSignInView', 'password_signin', { path: '/' });
|
||||
redirectPath = getStatusRedirect('/', 'Success!', 'You are now signed in.');
|
||||
} else {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/password_signin',
|
||||
'Hmm... Something went wrong.',
|
||||
'You could not be signed in.'
|
||||
);
|
||||
}
|
||||
|
||||
return redirectPath;
|
||||
}
|
||||
|
||||
export async function signUp(formData: FormData) {
|
||||
const callbackURL = getURL('/auth/callback');
|
||||
|
||||
const email = String(formData.get('email')).trim();
|
||||
const password = String(formData.get('password')).trim();
|
||||
let redirectPath: string;
|
||||
|
||||
if (!isValidEmail(email)) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/signup',
|
||||
'Invalid email address.',
|
||||
'Please try again.'
|
||||
);
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
const { error, data } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
emailRedirectTo: callbackURL
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/signup',
|
||||
'Sign up failed.',
|
||||
error.message
|
||||
);
|
||||
} else if (data.session) {
|
||||
redirectPath = getStatusRedirect('/', 'Success!', 'You are now signed in.');
|
||||
} else if (
|
||||
data.user &&
|
||||
data.user.identities &&
|
||||
data.user.identities.length == 0
|
||||
) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/signup',
|
||||
'Sign up failed.',
|
||||
'There is already an account associated with this email address. Try resetting your password.'
|
||||
);
|
||||
} else if (data.user) {
|
||||
redirectPath = getStatusRedirect(
|
||||
'/',
|
||||
'Success!',
|
||||
'Please check your email for a confirmation link. You may now close this tab.'
|
||||
);
|
||||
} else {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/signup',
|
||||
'Hmm... Something went wrong.',
|
||||
'You could not be signed up.'
|
||||
);
|
||||
}
|
||||
|
||||
return redirectPath;
|
||||
}
|
||||
|
||||
export async function updatePassword(formData: FormData) {
|
||||
const password = String(formData.get('password')).trim();
|
||||
const passwordConfirm = String(formData.get('passwordConfirm')).trim();
|
||||
let redirectPath: string;
|
||||
|
||||
// Check that the password and confirmation match
|
||||
if (password !== passwordConfirm) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/update_password',
|
||||
'Your password could not be updated.',
|
||||
'Passwords do not match.'
|
||||
);
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
const { error, data } = await supabase.auth.updateUser({
|
||||
password
|
||||
});
|
||||
|
||||
if (error) {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/update_password',
|
||||
'Your password could not be updated.',
|
||||
error.message
|
||||
);
|
||||
} else if (data.user) {
|
||||
redirectPath = getStatusRedirect(
|
||||
'/',
|
||||
'Success!',
|
||||
'Your password has been updated.'
|
||||
);
|
||||
} else {
|
||||
redirectPath = getErrorRedirect(
|
||||
'/dashboard/signin/update_password',
|
||||
'Hmm... Something went wrong.',
|
||||
'Your password could not be updated.'
|
||||
);
|
||||
}
|
||||
|
||||
return redirectPath;
|
||||
}
|
||||
|
||||
export async function updateEmail(formData: FormData) {
|
||||
// Get form data
|
||||
const newEmail = String(formData.get('newEmail')).trim();
|
||||
|
||||
// Check that the email is valid
|
||||
if (!isValidEmail(newEmail)) {
|
||||
return getErrorRedirect(
|
||||
'/dashboard/settings',
|
||||
'Your email could not be updated.',
|
||||
'Invalid email address.'
|
||||
);
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
|
||||
const callbackUrl = getURL(
|
||||
getStatusRedirect(
|
||||
'/dashboard/settings',
|
||||
'Success!',
|
||||
`Your email has been updated.`
|
||||
)
|
||||
);
|
||||
|
||||
const { error } = await supabase.auth.updateUser(
|
||||
{ email: newEmail },
|
||||
{
|
||||
emailRedirectTo: callbackUrl
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return getErrorRedirect(
|
||||
'/dashboard/settings',
|
||||
'Your email could not be updated.',
|
||||
error.message
|
||||
);
|
||||
} else {
|
||||
return getStatusRedirect(
|
||||
'/dashboard/settings',
|
||||
'Confirmation emails sent.',
|
||||
`You will need to confirm the update by clicking the links sent to both the old and new email addresses.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateName(formData: FormData) {
|
||||
// Get form data
|
||||
const fullName = String(formData.get('fullName')).trim();
|
||||
|
||||
const supabase = createClient();
|
||||
const { error, data } = await supabase.auth.updateUser({
|
||||
data: { full_name: fullName }
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return getErrorRedirect(
|
||||
'/dashboard/settings',
|
||||
'Your name could not be updated.',
|
||||
error.message
|
||||
);
|
||||
} else if (data.user) {
|
||||
return getStatusRedirect(
|
||||
'/dashboard/settings',
|
||||
'Success!',
|
||||
'Your name has been updated.'
|
||||
);
|
||||
} else {
|
||||
return getErrorRedirect(
|
||||
'/dashboard/settings',
|
||||
'Hmm... Something went wrong.',
|
||||
'Your name could not be updated.'
|
||||
);
|
||||
}
|
||||
}
|
||||
49
boilerplate-chakra-pro-main/utils/auth-helpers/settings.ts
Normal file
49
boilerplate-chakra-pro-main/utils/auth-helpers/settings.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// Boolean toggles to determine which auth types are allowed
|
||||
const allowOauth = true;
|
||||
const allowEmail = true;
|
||||
const allowPassword = true;
|
||||
|
||||
// Boolean toggle to determine whether auth interface should route through server or client
|
||||
// (Currently set to false because screen sometimes flickers with server redirects)
|
||||
const allowServerRedirect = false;
|
||||
|
||||
// Check that at least one of allowPassword and allowEmail is true
|
||||
if (!allowPassword && !allowEmail)
|
||||
throw new Error('At least one of allowPassword and allowEmail must be true');
|
||||
|
||||
export const getAuthTypes = () => {
|
||||
return { allowOauth, allowEmail, allowPassword };
|
||||
};
|
||||
|
||||
export const getViewTypes = () => {
|
||||
// Define the valid view types
|
||||
let viewTypes: string[] = [];
|
||||
if (allowEmail) {
|
||||
viewTypes = [...viewTypes, 'email_signin'];
|
||||
}
|
||||
if (allowPassword) {
|
||||
viewTypes = [
|
||||
...viewTypes,
|
||||
'password_signin',
|
||||
'forgot_password',
|
||||
'update_password',
|
||||
'signup'
|
||||
];
|
||||
}
|
||||
|
||||
return viewTypes;
|
||||
};
|
||||
|
||||
export const getDefaultSignInView = (preferredSignInView: string | null) => {
|
||||
// Define the default sign in view
|
||||
let defaultView = allowPassword ? 'password_signin' : 'email_signin';
|
||||
if (preferredSignInView && getViewTypes().includes(preferredSignInView)) {
|
||||
defaultView = preferredSignInView;
|
||||
}
|
||||
|
||||
return defaultView;
|
||||
};
|
||||
|
||||
export const getRedirectMethod = () => {
|
||||
return allowServerRedirect ? 'server' : 'client';
|
||||
};
|
||||
6
boilerplate-chakra-pro-main/utils/cn.ts
Normal file
6
boilerplate-chakra-pro-main/utils/cn.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
39
boilerplate-chakra-pro-main/utils/cookies.ts
Normal file
39
boilerplate-chakra-pro-main/utils/cookies.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { parse, serialize } from 'cookie';
|
||||
|
||||
// Function to parse cookies from the request
|
||||
export function parseCookies(req: NextRequest) {
|
||||
const cookieHeader = req.headers.get('cookie');
|
||||
return cookieHeader ? parse(cookieHeader) : {};
|
||||
}
|
||||
|
||||
// Function to set cookies in the response
|
||||
export function setCookie(
|
||||
res: NextResponse,
|
||||
name: string,
|
||||
value: any,
|
||||
options: any = {}
|
||||
) {
|
||||
const stringValue =
|
||||
typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value);
|
||||
|
||||
if (options.maxAge) {
|
||||
options.expires = new Date(Date.now() + options.maxAge * 1000);
|
||||
}
|
||||
|
||||
res.cookies.set(name, stringValue, options);
|
||||
}
|
||||
|
||||
// Function to get a specific cookie
|
||||
export function getCookie(req: NextRequest, name: string) {
|
||||
const cookies = parseCookies(req);
|
||||
const value = cookies[name];
|
||||
if (value && value.startsWith('j:')) {
|
||||
try {
|
||||
return JSON.parse(value.slice(2));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
139
boilerplate-chakra-pro-main/utils/helpers.ts
Normal file
139
boilerplate-chakra-pro-main/utils/helpers.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Database } from '@/types/types_db';
|
||||
|
||||
type Price = Database['public']['Tables']['prices']['Row'];
|
||||
|
||||
export const getURL = (path?: string) => {
|
||||
let url =
|
||||
process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
|
||||
process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
|
||||
'http://localhost:3000/';
|
||||
// Make sure to include `https://` when not localhost.
|
||||
url = url.includes('http') ? url : `https://${url}`;
|
||||
// Make sure to including trailing `/`.
|
||||
url = url.charAt(url.length - 1) === '/' ? url : `${url}/`;
|
||||
|
||||
if (path) {
|
||||
path = path.replace(/^\/+/, '');
|
||||
|
||||
// Concatenate the URL and the path.
|
||||
return path ? `${url}/${path}` : url;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const postData = async ({
|
||||
url,
|
||||
data
|
||||
}: {
|
||||
url: string;
|
||||
data?: { price: Price };
|
||||
}) => {
|
||||
console.log('posting,', url, data);
|
||||
|
||||
const res = 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 postData', { url, data, res });
|
||||
|
||||
throw Error(res.statusText);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
};
|
||||
|
||||
export const toDateTime = (secs: number) => {
|
||||
var t = new Date('1970-01-01T00:30:00Z'); // Unix epoch start.
|
||||
t.setSeconds(secs);
|
||||
return t;
|
||||
};
|
||||
|
||||
export const calculateTrialEndUnixTimestamp = (
|
||||
trialPeriodDays: number | null | undefined
|
||||
) => {
|
||||
// Check if trialPeriodDays is null, undefined, or less than 2 days
|
||||
if (
|
||||
trialPeriodDays === null ||
|
||||
trialPeriodDays === undefined ||
|
||||
trialPeriodDays < 2
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const currentDate = new Date(); // Current date and time
|
||||
const trialEnd = new Date(
|
||||
currentDate.getTime() + (trialPeriodDays + 1) * 24 * 60 * 60 * 1000
|
||||
); // Add trial days
|
||||
return Math.floor(trialEnd.getTime() / 1000); // Convert to Unix timestamp in seconds
|
||||
};
|
||||
|
||||
const toastKeyMap: { [key: string]: string[] } = {
|
||||
status: ['status', 'status_description'],
|
||||
error: ['error', 'error_description']
|
||||
};
|
||||
|
||||
const getToastRedirect = (
|
||||
path: string,
|
||||
toastType: string,
|
||||
toastName: string,
|
||||
toastDescription: string = '',
|
||||
disableButton: boolean = false,
|
||||
arbitraryParams: string = ''
|
||||
): string => {
|
||||
const [nameKey, descriptionKey] = toastKeyMap[toastType];
|
||||
|
||||
let redirectPath = `${path}?${nameKey}=${encodeURIComponent(toastName)}`;
|
||||
|
||||
if (toastDescription) {
|
||||
redirectPath += `&${descriptionKey}=${encodeURIComponent(
|
||||
toastDescription
|
||||
)}`;
|
||||
}
|
||||
|
||||
if (disableButton) {
|
||||
redirectPath += `&disable_button=true`;
|
||||
}
|
||||
|
||||
if (arbitraryParams) {
|
||||
redirectPath += `&${arbitraryParams}`;
|
||||
}
|
||||
|
||||
return redirectPath;
|
||||
};
|
||||
|
||||
export const getStatusRedirect = (
|
||||
path: string,
|
||||
statusName: string,
|
||||
statusDescription: string = '',
|
||||
disableButton: boolean = false,
|
||||
arbitraryParams: string = ''
|
||||
) =>
|
||||
getToastRedirect(
|
||||
path,
|
||||
'status',
|
||||
statusName,
|
||||
statusDescription,
|
||||
disableButton,
|
||||
arbitraryParams
|
||||
);
|
||||
|
||||
export const getErrorRedirect = (
|
||||
path: string,
|
||||
errorName: string,
|
||||
errorDescription: string = '',
|
||||
disableButton: boolean = false,
|
||||
arbitraryParams: string = ''
|
||||
) =>
|
||||
getToastRedirect(
|
||||
path,
|
||||
'error',
|
||||
errorName,
|
||||
errorDescription,
|
||||
disableButton,
|
||||
arbitraryParams
|
||||
);
|
||||
39
boilerplate-chakra-pro-main/utils/navigation.tsx
Normal file
39
boilerplate-chakra-pro-main/utils/navigation.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { IRoute } from '@/types/types'
|
||||
// NextJS Requirement
|
||||
export const isWindowAvailable = () => typeof window !== 'undefined'
|
||||
|
||||
export const findCurrentRoute = (
|
||||
routes: IRoute[],
|
||||
pathname: string,
|
||||
): IRoute | undefined => {
|
||||
for (let route of routes) {
|
||||
if (route.items) {
|
||||
const found = findCurrentRoute(route.items, pathname)
|
||||
if (found) return found
|
||||
}
|
||||
if (pathname?.match(route.path) && route) {
|
||||
return route
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getActiveRoute = (routes: IRoute[], pathname: string): string => {
|
||||
const route = findCurrentRoute(routes, pathname)
|
||||
return route?.name || 'Default Brand Text'
|
||||
}
|
||||
|
||||
export const getActiveNavbar = (
|
||||
routes: IRoute[],
|
||||
pathname: string,
|
||||
): boolean => {
|
||||
const route = findCurrentRoute(routes, pathname)
|
||||
if (route?.secondary) return route?.secondary
|
||||
else return false
|
||||
}
|
||||
|
||||
export const getActiveNavbarText = (
|
||||
routes: IRoute[],
|
||||
pathname: string,
|
||||
): string | boolean => {
|
||||
return getActiveRoute(routes, pathname) || false
|
||||
}
|
||||
88
boilerplate-chakra-pro-main/utils/streams/chatStream.ts
Normal file
88
boilerplate-chakra-pro-main/utils/streams/chatStream.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import endent from 'endent';
|
||||
import {
|
||||
createParser,
|
||||
ParsedEvent,
|
||||
ReconnectInterval,
|
||||
} from 'eventsource-parser';
|
||||
|
||||
const createPrompt = (inputMessage: string) => {
|
||||
const data = (inputMessage: string) => {
|
||||
return endent`
|
||||
You are ChatGPT, but if you are asked what your name is, you present yourself as Horizon UI Boilerplate and you can be bought from your website https://horizon-ui.com/boilerplate, and the link should be bold. Also, you are very friendly and formal. The generated content must be in markdown format and it must include all markdown characteristics, but it shouldn't be rendered. The title must be bold, and there should be a between every paragraph or title. Do not include information about console logs or print messages.
|
||||
${inputMessage}
|
||||
`;
|
||||
};
|
||||
|
||||
if (inputMessage) {
|
||||
return data(inputMessage);
|
||||
}
|
||||
};
|
||||
|
||||
export async function OpenAIStream (
|
||||
inputMessage: string,
|
||||
model: string,
|
||||
key: string | undefined,
|
||||
) {
|
||||
const prompt = createPrompt(inputMessage);
|
||||
|
||||
const system = { role: 'system', content: prompt };
|
||||
|
||||
const res = await fetch(`https://api.openai.com/v1/chat/completions`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${key || process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [system],
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
}),
|
||||
});
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
if (res.status !== 200) {
|
||||
const statusText = res.statusText;
|
||||
const result = await res.body?.getReader().read();
|
||||
throw new Error(
|
||||
`OpenAI API returned an error: ${
|
||||
decoder.decode(result?.value) || statusText
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const onParse = (event: ParsedEvent | ReconnectInterval) => {
|
||||
if (event.type === 'event') {
|
||||
const data = event.data;
|
||||
|
||||
if (data === '[DONE]') {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const text = json.choices[0].delta.content;
|
||||
const queue = encoder.encode(text);
|
||||
controller.enqueue(queue);
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parser = createParser(onParse);
|
||||
|
||||
for await (const chunk of res.body as any) {
|
||||
parser.feed(decoder.decode(chunk));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
90
boilerplate-chakra-pro-main/utils/streams/complexity.ts
Normal file
90
boilerplate-chakra-pro-main/utils/streams/complexity.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import endent from 'endent';
|
||||
import {
|
||||
createParser,
|
||||
ParsedEvent,
|
||||
ReconnectInterval,
|
||||
} from 'eventsource-parser';
|
||||
|
||||
const createPrompt = (
|
||||
inputLanguage: string,
|
||||
outputLanguage: string,
|
||||
inputCode: string,
|
||||
) => {
|
||||
return endent`
|
||||
You are an expert programmer in all programming languages.
|
||||
You know very well to calculate the complexity of the code. Calculate the code complexity of the following code.
|
||||
${inputCode}
|
||||
|
||||
`;
|
||||
};
|
||||
|
||||
export const OpenAIStreamComplexity = async (
|
||||
inputLanguage: string,
|
||||
outputLanguage: string,
|
||||
inputCode: string,
|
||||
model: string,
|
||||
key: string,
|
||||
) => {
|
||||
const prompt = createPrompt(inputLanguage, outputLanguage, inputCode);
|
||||
|
||||
const system = { role: 'system', content: prompt };
|
||||
|
||||
const res = await fetch(`https://api.openai.com/v1/chat/completions`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${key || process.env.OPENAI_API_KEY}`,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [system],
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
}),
|
||||
});
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
if (res.status !== 200) {
|
||||
const statusText = res.statusText;
|
||||
const result = await res.body?.getReader().read();
|
||||
throw new Error(
|
||||
`OpenAI API returned an error: ${
|
||||
decoder.decode(result?.value) || statusText
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const onParse = (event: ParsedEvent | ReconnectInterval) => {
|
||||
if (event.type === 'event') {
|
||||
const data = event.data;
|
||||
|
||||
if (data === '[DONE]') {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const text = json.choices[0].delta.content;
|
||||
const queue = encoder.encode(text);
|
||||
controller.enqueue(queue);
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parser = createParser(onParse);
|
||||
|
||||
for await (const chunk of res.body as any) {
|
||||
parser.feed(decoder.decode(chunk));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
93
boilerplate-chakra-pro-main/utils/streams/essayStream.ts
Normal file
93
boilerplate-chakra-pro-main/utils/streams/essayStream.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import endent from 'endent';
|
||||
import {
|
||||
createParser,
|
||||
ParsedEvent,
|
||||
ReconnectInterval,
|
||||
} from 'eventsource-parser';
|
||||
|
||||
const createPrompt = (topic: string, words: string, essayType: string) => {
|
||||
const data = (topic: any, words: string, essayType: string) => {
|
||||
return endent`
|
||||
You are an expert formal essay writer and generator.
|
||||
You know very well all types of essays. Generate an formal ${essayType} essay about ${topic}, which has a number of maximum ${words} words.
|
||||
The generated content should NOT be longer than ${words} words.
|
||||
The essay must be in markdown format but not rendered, it must include all markdown characteristics. The title must be bold, and there should be a between every paragraph.
|
||||
Do not include informations about console logs or print messages.
|
||||
`;
|
||||
};
|
||||
|
||||
if (essayType) {
|
||||
return data(topic, words, essayType);
|
||||
}
|
||||
};
|
||||
|
||||
export const OpenAIStream = async (
|
||||
topic: string,
|
||||
essayType: string,
|
||||
words: string,
|
||||
model: string,
|
||||
key: string | undefined,
|
||||
) => {
|
||||
const prompt = createPrompt(topic, words, essayType);
|
||||
|
||||
const system = { role: 'system', content: prompt };
|
||||
|
||||
const res = await fetch(`https://api.openai.com/v1/chat/completions`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${key || process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [system],
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
}),
|
||||
});
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
if (res.status !== 200) {
|
||||
const statusText = res.statusText;
|
||||
const result = await res.body?.getReader().read();
|
||||
throw new Error(
|
||||
`OpenAI API returned an error: ${
|
||||
decoder.decode(result?.value) || statusText
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const onParse = (event: ParsedEvent | ReconnectInterval) => {
|
||||
if (event.type === 'event') {
|
||||
const data = event.data;
|
||||
|
||||
if (data === '[DONE]') {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const text = json.choices[0].delta.content;
|
||||
const queue = encoder.encode(text);
|
||||
controller.enqueue(queue);
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parser = createParser(onParse);
|
||||
|
||||
for await (const chunk of res.body as any) {
|
||||
parser.feed(decoder.decode(chunk));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
118
boilerplate-chakra-pro-main/utils/streams/index.ts
Normal file
118
boilerplate-chakra-pro-main/utils/streams/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import endent from 'endent';
|
||||
import {
|
||||
createParser,
|
||||
ParsedEvent,
|
||||
ReconnectInterval,
|
||||
} from 'eventsource-parser';
|
||||
|
||||
const createPrompt = (
|
||||
inputLanguage: string,
|
||||
outputLanguage: string,
|
||||
inputCode: string,
|
||||
) => {
|
||||
const data = (inputCode: any, type: string) => {
|
||||
return endent`
|
||||
You are an expert programmer in all programming languages.
|
||||
You know very well algorithms. You will explain how the code works.
|
||||
The explanation must me in markdown format but not rendered, it must include all markdown characteristics.
|
||||
Do not include informations about console logs or print messages. Explain the following code ${type !== 'persona' && 'as I am a' + type}:
|
||||
|
||||
${inputCode}
|
||||
|
||||
`;
|
||||
};
|
||||
|
||||
switch (outputLanguage) {
|
||||
case 'persona':
|
||||
return data(inputCode, 'persona');
|
||||
case 'teacher':
|
||||
return data(inputCode, 'teacher');
|
||||
case '5':
|
||||
return data(inputCode, '5 years boy');
|
||||
case 'beginner':
|
||||
return data(inputCode, 'beginner programmer');
|
||||
case 'nasa':
|
||||
return data(inputCode, 'very intelligent PHD professor at MIT');
|
||||
case 'pizza-delivery-guy':
|
||||
return data(inputCode, 'pizza delivery guy');
|
||||
case 'bus-driver':
|
||||
return data(inputCode, 'bus driver');
|
||||
case 'magician':
|
||||
return data(inputCode, 'magician');
|
||||
case 'barista':
|
||||
return data(inputCode, 'barista');
|
||||
case 'doctor':
|
||||
return data(inputCode, 'doctor');
|
||||
}
|
||||
};
|
||||
|
||||
export const OpenAIStream = async (
|
||||
inputLanguage: string,
|
||||
outputLanguage: string,
|
||||
inputCode: string,
|
||||
model: string,
|
||||
key: string,
|
||||
) => {
|
||||
const prompt = createPrompt(inputLanguage, outputLanguage, inputCode);
|
||||
|
||||
const system = { role: 'system', content: prompt };
|
||||
|
||||
const res = await fetch(`https://api.openai.com/v1/chat/completions`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${key || process.env.OPENAI_API_KEY}`,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [system],
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
}),
|
||||
});
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
if (res.status !== 200) {
|
||||
const statusText = res.statusText;
|
||||
const result = await res.body?.getReader().read();
|
||||
throw new Error(
|
||||
`OpenAI API returned an error: ${
|
||||
decoder.decode(result?.value) || statusText
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const onParse = (event: ParsedEvent | ReconnectInterval) => {
|
||||
if (event.type === 'event') {
|
||||
const data = event.data;
|
||||
|
||||
if (data === '[DONE]') {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const text = json.choices[0].delta.content;
|
||||
const queue = encoder.encode(text);
|
||||
controller.enqueue(queue);
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parser = createParser(onParse);
|
||||
|
||||
for await (const chunk of res.body as any) {
|
||||
parser.feed(decoder.decode(chunk));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
100
boilerplate-chakra-pro-main/utils/streams/premiumEssayStream.ts
Normal file
100
boilerplate-chakra-pro-main/utils/streams/premiumEssayStream.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import endent from 'endent';
|
||||
import {
|
||||
createParser,
|
||||
ParsedEvent,
|
||||
ReconnectInterval,
|
||||
} from 'eventsource-parser';
|
||||
|
||||
const createPrompt = (words:string,topic: string, essayType: string, tone:string, citation:string, level:string,citations:boolean) => {
|
||||
const data = (words:string,topic: any, essayType: string, tone:string, citation:string, level:string,citations:boolean) => {
|
||||
return endent`
|
||||
You are an expert formal essay writer and generator.
|
||||
You know very well all types of essays. Generate an ${essayType} essay about ${topic}, in ${words} words.
|
||||
The generated content must have at least ${words} words.
|
||||
The essay should be written on an ${tone} tone and be written for the ${level} academic level.
|
||||
Also, the citation format of the essay should be ${citation}.
|
||||
Throught the project, you ${citations ? "must" : "must not"} use citations, quoting books or famous people regarding the subject.
|
||||
The essay must be in markdown format but not rendered, it must include all markdown characteristics.The title must be bold, and there should be a   between every paragraph.
|
||||
Do not include informations about console logs or print messages.
|
||||
`;
|
||||
};
|
||||
|
||||
if (essayType) {
|
||||
return data(words,topic, essayType, tone, citation, level,citations);
|
||||
}
|
||||
};
|
||||
|
||||
export const OpenAIStream = async (
|
||||
words:string,
|
||||
topic: string,
|
||||
essayType: string,
|
||||
tone: string,
|
||||
citation: string,
|
||||
level: string,
|
||||
citations:boolean,
|
||||
model: string,
|
||||
key: string | undefined,
|
||||
) => {
|
||||
const prompt = createPrompt(words, topic, essayType, tone, citation, level, citations);
|
||||
|
||||
const system = { role: 'system', content: prompt };
|
||||
|
||||
const res = await fetch(`https://api.openai.com/v1/chat/completions`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${key || process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [system],
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
}),
|
||||
});
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
if (res.status !== 200) {
|
||||
const statusText = res.statusText;
|
||||
const result = await res.body?.getReader().read();
|
||||
throw new Error(
|
||||
`OpenAI API returned an error: ${
|
||||
decoder.decode(result?.value) || statusText
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const onParse = (event: ParsedEvent | ReconnectInterval) => {
|
||||
if (event.type === 'event') {
|
||||
const data = event.data;
|
||||
|
||||
if (data === '[DONE]') {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const text = json.choices[0].delta.content;
|
||||
const queue = encoder.encode(text);
|
||||
controller.enqueue(queue);
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parser = createParser(onParse);
|
||||
|
||||
for await (const chunk of res.body as any) {
|
||||
parser.feed(decoder.decode(chunk));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
15
boilerplate-chakra-pro-main/utils/stripe/client.ts
Normal file
15
boilerplate-chakra-pro-main/utils/stripe/client.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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_LIVE ??
|
||||
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ??
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
return stripePromise;
|
||||
};
|
||||
18
boilerplate-chakra-pro-main/utils/stripe/config.ts
Normal file
18
boilerplate-chakra-pro-main/utils/stripe/config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import Stripe from 'stripe';
|
||||
|
||||
export const stripe = new Stripe(
|
||||
process.env.STRIPE_SECRET_KEY_LIVE ?? process.env.STRIPE_SECRET_KEY ?? '',
|
||||
{
|
||||
// https://github.com/stripe/stripe-node#configuration
|
||||
// https://stripe.com/docs/api/versioning
|
||||
// @ts-ignore
|
||||
apiVersion: null,
|
||||
// Register this as an official Stripe plugin.
|
||||
// https://stripe.com/docs/building-plugins#setappinfo
|
||||
appInfo: {
|
||||
name: 'Horizon AI Boilerplate',
|
||||
version: '1.1.0',
|
||||
url: 'https://github.com/horizon-ui/shadcn-nextjs-boilerplate'
|
||||
}
|
||||
}
|
||||
);
|
||||
181
boilerplate-chakra-pro-main/utils/stripe/server.ts
Normal file
181
boilerplate-chakra-pro-main/utils/stripe/server.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
'use server';
|
||||
|
||||
import Stripe from 'stripe';
|
||||
import { stripe } from '@/utils/stripe/config';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { createOrRetrieveCustomer } from '@/utils/supabase/admin';
|
||||
import {
|
||||
getURL,
|
||||
getErrorRedirect,
|
||||
calculateTrialEndUnixTimestamp
|
||||
} from '@/utils/helpers';
|
||||
import { Tables } from '@/types/types_db';
|
||||
|
||||
type Price = Tables<'prices'>;
|
||||
|
||||
type CheckoutResponse = {
|
||||
errorRedirect?: string;
|
||||
sessionId?: string;
|
||||
};
|
||||
|
||||
export async function checkoutWithStripe(
|
||||
price: Price,
|
||||
redirectPath: string = '/account'
|
||||
): Promise<CheckoutResponse> {
|
||||
try {
|
||||
// Get the user from Supabase auth
|
||||
const supabase = createClient();
|
||||
const {
|
||||
error,
|
||||
data: { user }
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (error || !user) {
|
||||
console.error(error);
|
||||
throw new Error('Could not get user session.');
|
||||
}
|
||||
|
||||
// Retrieve or create the customer in Stripe
|
||||
let customer: string;
|
||||
try {
|
||||
customer = await createOrRetrieveCustomer({
|
||||
uuid: user?.id || '',
|
||||
email: user?.email || ''
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Unable to access customer record.');
|
||||
}
|
||||
|
||||
let params: Stripe.Checkout.SessionCreateParams = {
|
||||
allow_promotion_codes: true,
|
||||
billing_address_collection: 'required',
|
||||
customer,
|
||||
customer_update: {
|
||||
address: 'auto'
|
||||
},
|
||||
line_items: [
|
||||
{
|
||||
price: price.id,
|
||||
quantity: 1
|
||||
}
|
||||
],
|
||||
cancel_url: getURL(),
|
||||
success_url: getURL(redirectPath)
|
||||
};
|
||||
|
||||
console.log(
|
||||
'Trial end:',
|
||||
calculateTrialEndUnixTimestamp(price.trial_period_days)
|
||||
);
|
||||
if (price.type === 'recurring') {
|
||||
params = {
|
||||
...params,
|
||||
mode: 'subscription',
|
||||
subscription_data: {
|
||||
trial_end: calculateTrialEndUnixTimestamp(price.trial_period_days)
|
||||
}
|
||||
};
|
||||
} else if (price.type === 'one_time') {
|
||||
params = {
|
||||
...params,
|
||||
mode: 'payment'
|
||||
};
|
||||
}
|
||||
|
||||
// Create a checkout session in Stripe
|
||||
let session;
|
||||
try {
|
||||
session = await stripe.checkout.sessions.create(params);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Unable to create checkout session.');
|
||||
}
|
||||
|
||||
// Instead of returning a Response, just return the data or error.
|
||||
if (session) {
|
||||
return { sessionId: session.id };
|
||||
} else {
|
||||
throw new Error('Unable to create checkout session.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return {
|
||||
errorRedirect: getErrorRedirect(
|
||||
redirectPath,
|
||||
error.message,
|
||||
'Please try again later or contact a system administrator.'
|
||||
)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
errorRedirect: getErrorRedirect(
|
||||
redirectPath,
|
||||
'An unknown error occurred.',
|
||||
'Please try again later or contact a system administrator.'
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function createStripePortal(currentPath: string) {
|
||||
try {
|
||||
const supabase = createClient();
|
||||
const {
|
||||
error,
|
||||
data: { user }
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
throw new Error('Could not get user session.');
|
||||
}
|
||||
|
||||
let customer;
|
||||
try {
|
||||
customer = await createOrRetrieveCustomer({
|
||||
uuid: user.id || '',
|
||||
email: user.email || ''
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Unable to access customer record.');
|
||||
}
|
||||
|
||||
if (!customer) {
|
||||
throw new Error('Could not get customer.');
|
||||
}
|
||||
|
||||
try {
|
||||
const { url } = await stripe.billingPortal.sessions.create({
|
||||
customer,
|
||||
return_url: getURL('/account')
|
||||
});
|
||||
if (!url) {
|
||||
throw new Error('Could not create billing portal');
|
||||
}
|
||||
return url;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Could not create billing portal');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error);
|
||||
return getErrorRedirect(
|
||||
currentPath,
|
||||
error.message,
|
||||
'Please try again later or contact a system administrator.'
|
||||
);
|
||||
} else {
|
||||
return getErrorRedirect(
|
||||
currentPath,
|
||||
'An unknown error occurred.',
|
||||
'Please try again later or contact a system administrator.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
185
boilerplate-chakra-pro-main/utils/supabase-admin.ts
Normal file
185
boilerplate-chakra-pro-main/utils/supabase-admin.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { toDateTime } from './helpers';
|
||||
import { stripe } from './stripe';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import Stripe from 'stripe';
|
||||
import type { Database } from '@/types_db';
|
||||
|
||||
type Product = Database['public']['Tables']['products']['Row'];
|
||||
type Price = Database['public']['Tables']['prices']['Row'];
|
||||
|
||||
// Note: supabaseAdmin uses the SERVICE_ROLE_KEY which you must only use in a secure server-side context
|
||||
// as it has admin privileges and overwrites RLS policies!
|
||||
const supabaseAdmin = createClient<Database>(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL || '',
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY || ''
|
||||
);
|
||||
|
||||
const upsertProductRecord = async (product: Stripe.Product) => {
|
||||
const productData: Product = {
|
||||
id: product.id,
|
||||
active: product.active,
|
||||
name: product.name,
|
||||
description: product.description ?? null,
|
||||
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 ?? null,
|
||||
type: price.type,
|
||||
unit_amount: price.unit_amount ?? null,
|
||||
interval: price.recurring?.interval ?? null,
|
||||
interval_count: price.recurring?.interval_count ?? null,
|
||||
trial_period_days: price.recurring?.trial_period_days ?? null,
|
||||
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) {
|
||||
// No customer record found, let's create one.
|
||||
const customerData: { metadata: { supabaseUUID: string }; email?: string } =
|
||||
{
|
||||
metadata: {
|
||||
supabaseUUID: uuid
|
||||
}
|
||||
};
|
||||
if (email) customerData.email = email;
|
||||
const customer = await stripe.customers.create(customerData);
|
||||
// Now insert the customer ID into our Supabase mapping table.
|
||||
const { error: supabaseError } = await supabaseAdmin
|
||||
.from('customers')
|
||||
.insert([{ id: uuid, stripe_customer_id: customer.id }]);
|
||||
if (supabaseError) throw supabaseError;
|
||||
console.log(`New customer created and inserted for ${uuid}.`);
|
||||
return customer.id;
|
||||
}
|
||||
return data.stripe_customer_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Copies the billing details from the payment method to the customer object.
|
||||
*/
|
||||
const copyBillingDetailsToCustomer = async (
|
||||
uuid: string,
|
||||
payment_method: Stripe.PaymentMethod
|
||||
) => {
|
||||
//Todo: check this assertion
|
||||
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
|
||||
) => {
|
||||
// Get customer's UUID from mapping table.
|
||||
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']
|
||||
});
|
||||
// Upsert the latest status of the subscription object.
|
||||
const subscriptionData: Database['public']['Tables']['subscriptions']['Insert'] =
|
||||
{
|
||||
id: subscription.id,
|
||||
user_id: uuid,
|
||||
metadata: subscription.metadata,
|
||||
status: subscription.status,
|
||||
price_id: subscription.items.data[0].price.id,
|
||||
//TODO check quantity on subscription
|
||||
// @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 user [${uuid}]`
|
||||
);
|
||||
|
||||
// For a new subscription copy the billing details to the customer object.
|
||||
// NOTE: This is a costly operation and should happen at the very end.
|
||||
if (createAction && subscription.default_payment_method && uuid)
|
||||
//@ts-ignore
|
||||
await copyBillingDetailsToCustomer(
|
||||
uuid,
|
||||
subscription.default_payment_method as Stripe.PaymentMethod
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
upsertProductRecord,
|
||||
upsertPriceRecord,
|
||||
createOrRetrieveCustomer,
|
||||
manageSubscriptionStatusChange
|
||||
};
|
||||
305
boilerplate-chakra-pro-main/utils/supabase/admin.ts
Normal file
305
boilerplate-chakra-pro-main/utils/supabase/admin.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import { toDateTime } from '@/utils/helpers';
|
||||
import { stripe } from '@/utils/stripe/config';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import Stripe from 'stripe';
|
||||
import type { Database, Tables, TablesInsert } from '@/types/types_db';
|
||||
|
||||
type Product = Tables<'products'>;
|
||||
type Price = Tables<'prices'>;
|
||||
|
||||
// Change to control trial period length
|
||||
const TRIAL_PERIOD_DAYS = 0;
|
||||
|
||||
// Note: supabaseAdmin uses the SERVICE_ROLE_KEY which you must only use in a secure server-side context
|
||||
// as it has admin privileges and overwrites RLS policies!
|
||||
const supabaseAdmin = createClient<Database>(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL || '',
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY || ''
|
||||
);
|
||||
|
||||
const upsertProductRecord = async (product: Stripe.Product) => {
|
||||
const productData: Product = {
|
||||
id: product.id,
|
||||
active: product.active,
|
||||
name: product.name,
|
||||
description: product.description ?? null,
|
||||
image: product.images?.[0] ?? null,
|
||||
metadata: product.metadata
|
||||
};
|
||||
|
||||
const { error: upsertError } = await supabaseAdmin
|
||||
.from('products')
|
||||
.upsert([productData]);
|
||||
if (upsertError)
|
||||
throw new Error(`Product insert/update failed: ${upsertError.message}`);
|
||||
console.log(`Product inserted/updated: ${product.id}`);
|
||||
};
|
||||
|
||||
const upsertPriceRecord = async (
|
||||
price: Stripe.Price,
|
||||
retryCount = 0,
|
||||
maxRetries = 3
|
||||
) => {
|
||||
const priceData: Price = {
|
||||
id: price.id,
|
||||
description: '',
|
||||
metadata: { shit: true },
|
||||
product_id: typeof price.product === 'string' ? price.product : '',
|
||||
active: price.active,
|
||||
currency: price.currency,
|
||||
type: price.type,
|
||||
unit_amount: price.unit_amount ?? null,
|
||||
interval: price.recurring?.interval ?? null,
|
||||
interval_count: price.recurring?.interval_count ?? null,
|
||||
trial_period_days: price.recurring?.trial_period_days ?? TRIAL_PERIOD_DAYS
|
||||
};
|
||||
|
||||
const { error: upsertError } = await supabaseAdmin
|
||||
.from('prices')
|
||||
.upsert([priceData]);
|
||||
|
||||
if (upsertError?.message.includes('foreign key constraint')) {
|
||||
if (retryCount < maxRetries) {
|
||||
console.log(`Retry attempt ${retryCount + 1} for price ID: ${price.id}`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
await upsertPriceRecord(price, retryCount + 1, maxRetries);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Price insert/update failed after ${maxRetries} retries: ${upsertError.message}`
|
||||
);
|
||||
}
|
||||
} else if (upsertError) {
|
||||
throw new Error(`Price insert/update failed: ${upsertError.message}`);
|
||||
} else {
|
||||
console.log(`Price inserted/updated: ${price.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteProductRecord = async (product: Stripe.Product) => {
|
||||
const { error: deletionError } = await supabaseAdmin
|
||||
.from('products')
|
||||
.delete()
|
||||
.eq('id', product.id);
|
||||
if (deletionError)
|
||||
throw new Error(`Product deletion failed: ${deletionError.message}`);
|
||||
console.log(`Product deleted: ${product.id}`);
|
||||
};
|
||||
|
||||
const deletePriceRecord = async (price: Stripe.Price) => {
|
||||
const { error: deletionError } = await supabaseAdmin
|
||||
.from('prices')
|
||||
.delete()
|
||||
.eq('id', price.id);
|
||||
if (deletionError)
|
||||
throw new Error(`Price deletion failed: ${deletionError.message}`);
|
||||
console.log(`Price deleted: ${price.id}`);
|
||||
};
|
||||
|
||||
const upsertCustomerToSupabase = async (uuid: string, customerId: string) => {
|
||||
const { error: upsertError } = await supabaseAdmin
|
||||
.from('customers')
|
||||
.upsert([{ id: uuid, stripe_customer_id: customerId }]);
|
||||
|
||||
if (upsertError)
|
||||
throw new Error(
|
||||
`Supabase customer record creation failed: ${upsertError.message}`
|
||||
);
|
||||
|
||||
return customerId;
|
||||
};
|
||||
|
||||
const createCustomerInStripe = async (uuid: string, email: string) => {
|
||||
const customerData = { metadata: { supabaseUUID: uuid }, email: email };
|
||||
const newCustomer = await stripe.customers.create(customerData);
|
||||
if (!newCustomer) throw new Error('Stripe customer creation failed.');
|
||||
|
||||
return newCustomer.id;
|
||||
};
|
||||
|
||||
const createOrRetrieveCustomer = async ({
|
||||
email,
|
||||
uuid
|
||||
}: {
|
||||
email: string;
|
||||
uuid: string;
|
||||
}) => {
|
||||
// Check if the customer already exists in Supabase
|
||||
const {
|
||||
data: existingSupabaseCustomer,
|
||||
error: queryError
|
||||
} = await supabaseAdmin
|
||||
.from('customers')
|
||||
.select('*')
|
||||
.eq('id', uuid)
|
||||
.maybeSingle();
|
||||
|
||||
if (queryError) {
|
||||
throw new Error(`Supabase customer lookup failed: ${queryError.message}`);
|
||||
}
|
||||
|
||||
// Retrieve the Stripe customer ID using the Supabase customer ID, with email fallback
|
||||
let stripeCustomerId: string | undefined;
|
||||
if (existingSupabaseCustomer?.stripe_customer_id) {
|
||||
const existingStripeCustomer = await stripe.customers.retrieve(
|
||||
existingSupabaseCustomer.stripe_customer_id
|
||||
);
|
||||
stripeCustomerId = existingStripeCustomer.id;
|
||||
} else {
|
||||
// If Stripe ID is missing from Supabase, try to retrieve Stripe customer ID by email
|
||||
const stripeCustomers = await stripe.customers.list({ email: email });
|
||||
stripeCustomerId =
|
||||
stripeCustomers.data.length > 0 ? stripeCustomers.data[0].id : undefined;
|
||||
}
|
||||
|
||||
// If still no stripeCustomerId, create a new customer in Stripe
|
||||
const stripeIdToInsert = stripeCustomerId
|
||||
? stripeCustomerId
|
||||
: await createCustomerInStripe(uuid, email);
|
||||
if (!stripeIdToInsert) throw new Error('Stripe customer creation failed.');
|
||||
|
||||
if (existingSupabaseCustomer && stripeCustomerId) {
|
||||
// If Supabase has a record but doesn't match Stripe, update Supabase record
|
||||
if (existingSupabaseCustomer.stripe_customer_id !== stripeCustomerId) {
|
||||
const { error: updateError } = await supabaseAdmin
|
||||
.from('customers')
|
||||
.update({ stripe_customer_id: stripeCustomerId })
|
||||
.eq('id', uuid);
|
||||
|
||||
if (updateError)
|
||||
throw new Error(
|
||||
`Supabase customer record update failed: ${updateError.message}`
|
||||
);
|
||||
console.warn(
|
||||
`Supabase customer record mismatched Stripe ID. Supabase record updated.`
|
||||
);
|
||||
}
|
||||
// If Supabase has a record and matches Stripe, return Stripe customer ID
|
||||
return stripeCustomerId;
|
||||
} else {
|
||||
console.warn(
|
||||
`Supabase customer record was missing. A new record was created.`
|
||||
);
|
||||
|
||||
// If Supabase has no record, create a new record and return Stripe customer ID
|
||||
const upsertedStripeCustomer = await upsertCustomerToSupabase(
|
||||
uuid,
|
||||
stripeIdToInsert
|
||||
);
|
||||
if (!upsertedStripeCustomer)
|
||||
throw new Error('Supabase customer record creation failed.');
|
||||
|
||||
return upsertedStripeCustomer;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Copies the billing details from the payment method to the customer object.
|
||||
*/
|
||||
const copyBillingDetailsToCustomer = async (
|
||||
uuid: string,
|
||||
payment_method: Stripe.PaymentMethod
|
||||
) => {
|
||||
//Todo: check this assertion
|
||||
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: updateError } = await supabaseAdmin
|
||||
.from('users')
|
||||
.update({
|
||||
billing_address: { ...address },
|
||||
payment_method: { ...payment_method[payment_method.type] }
|
||||
})
|
||||
.eq('id', uuid);
|
||||
if (updateError)
|
||||
throw new Error(`Customer update failed: ${updateError.message}`);
|
||||
};
|
||||
|
||||
const manageSubscriptionStatusChange = async (
|
||||
subscriptionId: string,
|
||||
customerId: string,
|
||||
createAction = false
|
||||
) => {
|
||||
// Get customer's UUID from mapping table.
|
||||
const {
|
||||
data: customerData,
|
||||
error: noCustomerError
|
||||
} = await supabaseAdmin
|
||||
.from('customers')
|
||||
.select('id')
|
||||
.eq('stripe_customer_id', customerId)
|
||||
.single();
|
||||
|
||||
if (noCustomerError)
|
||||
throw new Error(`Customer lookup failed: ${noCustomerError.message}`);
|
||||
|
||||
const { id: uuid } = customerData!;
|
||||
|
||||
const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
|
||||
expand: ['default_payment_method']
|
||||
});
|
||||
// Upsert the latest status of the subscription object.
|
||||
const subscriptionData: TablesInsert<'subscriptions'> = {
|
||||
id: subscription.id,
|
||||
user_id: uuid,
|
||||
metadata: subscription.metadata,
|
||||
status: subscription.status,
|
||||
price_id: subscription.items.data[0].price.id,
|
||||
//TODO check quantity on subscription
|
||||
// @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: upsertError } = await supabaseAdmin
|
||||
.from('subscriptions')
|
||||
.upsert([subscriptionData]);
|
||||
if (upsertError)
|
||||
throw new Error(
|
||||
`Subscription insert/update failed: ${upsertError.message}`
|
||||
);
|
||||
console.log(
|
||||
`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`
|
||||
);
|
||||
|
||||
// For a new subscription copy the billing details to the customer object.
|
||||
// NOTE: This is a costly operation and should happen at the very end.
|
||||
if (createAction && subscription.default_payment_method && uuid)
|
||||
//@ts-ignore
|
||||
await copyBillingDetailsToCustomer(
|
||||
uuid,
|
||||
subscription.default_payment_method as Stripe.PaymentMethod
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
upsertProductRecord,
|
||||
upsertPriceRecord,
|
||||
deleteProductRecord,
|
||||
deletePriceRecord,
|
||||
createOrRetrieveCustomer,
|
||||
manageSubscriptionStatusChange
|
||||
};
|
||||
8
boilerplate-chakra-pro-main/utils/supabase/client.ts
Normal file
8
boilerplate-chakra-pro-main/utils/supabase/client.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createBrowserClient } from '@supabase/ssr';
|
||||
|
||||
export function createClient() {
|
||||
return createBrowserClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
);
|
||||
}
|
||||
84
boilerplate-chakra-pro-main/utils/supabase/middleware.ts
Normal file
84
boilerplate-chakra-pro-main/utils/supabase/middleware.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { createServerClient, type CookieOptions } from '@supabase/ssr';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export const createClient = (request: NextRequest) => {
|
||||
// Create an unmodified response
|
||||
let response = NextResponse.next({
|
||||
request: {
|
||||
headers: request.headers
|
||||
}
|
||||
});
|
||||
|
||||
const supabase = createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
{
|
||||
cookies: {
|
||||
get(name: string) {
|
||||
return request.cookies.get(name)?.value;
|
||||
},
|
||||
set(name: string, value: string, options: CookieOptions) {
|
||||
// If the cookie is updated, update the cookies for the request and response
|
||||
request.cookies.set({
|
||||
name,
|
||||
value,
|
||||
...options
|
||||
});
|
||||
response = NextResponse.next({
|
||||
request: {
|
||||
headers: request.headers
|
||||
}
|
||||
});
|
||||
response.cookies.set({
|
||||
name,
|
||||
value,
|
||||
...options
|
||||
});
|
||||
},
|
||||
remove(name: string, options: CookieOptions) {
|
||||
// If the cookie is removed, update the cookies for the request and response
|
||||
request.cookies.set({
|
||||
name,
|
||||
value: '',
|
||||
...options
|
||||
});
|
||||
response = NextResponse.next({
|
||||
request: {
|
||||
headers: request.headers
|
||||
}
|
||||
});
|
||||
response.cookies.set({
|
||||
name,
|
||||
value: '',
|
||||
...options
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return { supabase, response };
|
||||
};
|
||||
|
||||
export const updateSession = async (request: NextRequest) => {
|
||||
try {
|
||||
// This `try/catch` block is only here for the interactive tutorial.
|
||||
// Feel free to remove once you have Supabase connected.
|
||||
const { supabase, response } = createClient(request);
|
||||
|
||||
// This will refresh session if expired - required for Server Components
|
||||
// https://supabase.com/docs/guides/auth/server-side/nextjs
|
||||
await supabase.auth.getUser();
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
// If you are here, a Supabase client could not be created!
|
||||
// This is likely because you have not set up environment variables.
|
||||
// Check out http://localhost:3000 for Next Steps.
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: request.headers
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
39
boilerplate-chakra-pro-main/utils/supabase/queries.ts
Normal file
39
boilerplate-chakra-pro-main/utils/supabase/queries.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { SupabaseClient } from '@supabase/supabase-js';
|
||||
import { cache } from 'react';
|
||||
|
||||
export const getUser = cache(async (supabase: SupabaseClient) => {
|
||||
const {
|
||||
data: { user }
|
||||
} = await supabase.auth.getUser();
|
||||
return user;
|
||||
});
|
||||
|
||||
export const getSubscription = cache(async (supabase: SupabaseClient) => {
|
||||
const { data: subscription, error } = await supabase
|
||||
.from('subscriptions')
|
||||
.select('*, prices(*, products(*))')
|
||||
.in('status', ['trialing', 'active'])
|
||||
.maybeSingle();
|
||||
|
||||
return subscription;
|
||||
});
|
||||
|
||||
export const getProducts = cache(async (supabase: SupabaseClient) => {
|
||||
const { data: products, error } = await supabase
|
||||
.from('products')
|
||||
.select('*, prices(*)')
|
||||
.eq('active', true)
|
||||
.eq('prices.active', true)
|
||||
.order('metadata->index')
|
||||
.order('unit_amount', { referencedTable: 'prices' });
|
||||
|
||||
return products;
|
||||
});
|
||||
|
||||
export const getUserDetails = cache(async (supabase: SupabaseClient) => {
|
||||
const { data: userDetails } = await supabase
|
||||
.from('users')
|
||||
.select('*')
|
||||
.single();
|
||||
return userDetails;
|
||||
});
|
||||
43
boilerplate-chakra-pro-main/utils/supabase/server.ts
Normal file
43
boilerplate-chakra-pro-main/utils/supabase/server.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { createServerClient, type CookieOptions } from '@supabase/ssr';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Database } from '@/types/types_db';
|
||||
|
||||
// Define a function to create a Supabase client for server-side operations
|
||||
// The function takes a cookie store created with next/headers cookies as an argument
|
||||
export const createClient = () => {
|
||||
const cookieStore = cookies();
|
||||
|
||||
return createServerClient<Database>(
|
||||
// Pass Supabase URL and anonymous key from the environment to the client
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
|
||||
// Define a cookies object with methods for interacting with the cookie store and pass it to the client
|
||||
{
|
||||
cookies: {
|
||||
// The get method is used to retrieve a cookie by its name
|
||||
get(name: string) {
|
||||
return cookieStore.get(name)?.value;
|
||||
},
|
||||
// The set method is used to set a cookie with a given name, value, and options
|
||||
set(name: string, value: string, options: CookieOptions) {
|
||||
try {
|
||||
cookieStore.set({ name, value, ...options });
|
||||
} catch (error) {
|
||||
// If the set method is called from a Server Component, an error may occur
|
||||
// This can be ignored if there is middleware refreshing user sessions
|
||||
}
|
||||
},
|
||||
// The remove method is used to delete a cookie by its name
|
||||
remove(name: string, options: CookieOptions) {
|
||||
try {
|
||||
cookieStore.set({ name, value: '', ...options });
|
||||
} catch (error) {
|
||||
// If the remove method is called from a Server Component, an error may occur
|
||||
// This can be ignored if there is middleware refreshing user { s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user