mirror of
https://github.com/vercel/commerce.git
synced 2025-05-18 23:46:58 +00:00
clean up unused code and create login callback api endpoints
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
93f46a3f90
commit
8333eb36fc
@ -1,27 +0,0 @@
|
|||||||
import { headers } from 'next/headers';
|
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export default async function AuthorizationPage() {
|
|
||||||
const headersList = headers();
|
|
||||||
const access = headersList.get('x-shop-access');
|
|
||||||
if (!access) {
|
|
||||||
console.log('ERROR: No access header');
|
|
||||||
throw new Error('No access header');
|
|
||||||
}
|
|
||||||
console.log('Authorize Access code header:', access);
|
|
||||||
if (access === 'denied') {
|
|
||||||
console.log('Access Denied for Auth');
|
|
||||||
throw new Error('No access allowed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mx-auto max-w-screen-2xl px-4">
|
|
||||||
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-8 dark:border-neutral-800 dark:bg-black md:p-12 lg:flex-row lg:gap-8">
|
|
||||||
<div className="h-full w-full">Loading...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import { LoginMessage } from 'components/auth/login-message';
|
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export default async function LoginPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mx-auto max-w-screen-2xl px-4">
|
|
||||||
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-8 dark:border-neutral-800 dark:bg-black md:p-12 lg:flex-row lg:gap-8">
|
|
||||||
<div className="h-full w-full">
|
|
||||||
<LoginMessage />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export default async function LogoutPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mx-auto max-w-screen-2xl px-4">
|
|
||||||
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-8 dark:border-neutral-800 dark:bg-black md:p-12 lg:flex-row lg:gap-8">
|
|
||||||
<div className="h-full w-full">Loading...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,20 +1,17 @@
|
|||||||
import { CheckCircleIcon, TruckIcon, ArrowLeftIcon } from '@heroicons/react/24/outline';
|
import { ArrowLeftIcon, CheckCircleIcon, TruckIcon } from '@heroicons/react/24/outline';
|
||||||
import Image from 'next/image';
|
|
||||||
import { Button } from 'components/button';
|
import { Button } from 'components/button';
|
||||||
|
import OrderSummary from 'components/orders/order-summary';
|
||||||
|
import OrderSummaryMobile from 'components/orders/order-summary-mobile';
|
||||||
|
import Price from 'components/price';
|
||||||
|
import Badge from 'components/ui/badge';
|
||||||
import { Card } from 'components/ui/card';
|
import { Card } from 'components/ui/card';
|
||||||
import Heading from 'components/ui/heading';
|
import Heading from 'components/ui/heading';
|
||||||
import Label from 'components/ui/label';
|
import Label from 'components/ui/label';
|
||||||
|
import Text from 'components/ui/text';
|
||||||
import { getCustomerOrder } from 'lib/shopify';
|
import { getCustomerOrder } from 'lib/shopify';
|
||||||
import { Fulfillment, Order } from 'lib/shopify/types';
|
import { Fulfillment, Order } from 'lib/shopify/types';
|
||||||
import Text from 'components/ui/text';
|
import Image from 'next/image';
|
||||||
import Price from 'components/price';
|
|
||||||
import Badge from 'components/ui/badge';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import OrderSummaryMobile from 'components/account/orders/order-summary-mobile';
|
|
||||||
import { Suspense } from 'react';
|
|
||||||
import OrderSummary from 'components/account/orders/order-summary';
|
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
function toPrintDate(date: string) {
|
function toPrintDate(date: string) {
|
||||||
return new Date(date).toLocaleDateString('en-US', {
|
return new Date(date).toLocaleDateString('en-US', {
|
||||||
@ -242,9 +239,7 @@ export default async function OrderPage({ params }: { params: { id: string } })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Suspense>
|
<OrderSummaryMobile order={order} />
|
||||||
<OrderSummaryMobile order={order} />
|
|
||||||
</Suspense>
|
|
||||||
<div className="mx-auto max-w-6xl p-6">
|
<div className="mx-auto max-w-6xl p-6">
|
||||||
<div className="mb-6 flex justify-between">
|
<div className="mb-6 flex justify-between">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import Image from 'next/image';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { getCustomerOrders } from 'lib/shopify';
|
|
||||||
import Text from 'components/ui/text';
|
|
||||||
import Price from 'components/price';
|
|
||||||
import Divider from 'components/divider';
|
|
||||||
import { Button } from 'components/button';
|
import { Button } from 'components/button';
|
||||||
import Heading from 'components/ui/heading';
|
import Divider from 'components/divider';
|
||||||
import Label from 'components/ui/label';
|
import Price from 'components/price';
|
||||||
import Badge from 'components/ui/badge';
|
import Badge from 'components/ui/badge';
|
||||||
import { Card } from 'components/ui/card';
|
import { Card } from 'components/ui/card';
|
||||||
|
import Heading from 'components/ui/heading';
|
||||||
export const runtime = 'edge';
|
import Label from 'components/ui/label';
|
||||||
|
import Text from 'components/ui/text';
|
||||||
|
import { getCustomerOrders } from 'lib/shopify';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
export default async function AccountPage() {
|
export default async function AccountPage() {
|
||||||
const orders = await getCustomerOrders();
|
const orders = await getCustomerOrders();
|
||||||
|
9
app/api/authorize/route.ts
Normal file
9
app/api/authorize/route.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { authorize, getOrigin } from 'lib/shopify/auth';
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest): Promise<NextResponse> {
|
||||||
|
const origin = getOrigin(request);
|
||||||
|
return await authorize(request, origin);
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
import Banner from 'components/banner';
|
import Banner from 'components/banner';
|
||||||
import Navbar from 'components/layout/navbar';
|
import Navbar from 'components/layout/navbar';
|
||||||
|
import { AuthProvider } from 'contexts/auth-context';
|
||||||
import { GeistSans } from 'geist/font/sans';
|
import { GeistSans } from 'geist/font/sans';
|
||||||
import { ensureStartsWith } from 'lib/utils';
|
import { ensureStartsWith } from 'lib/utils';
|
||||||
import { ReactNode, Suspense } from 'react';
|
import { ReactNode, Suspense } from 'react';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
import { AuthProvider } from 'contexts/auth-context';
|
|
||||||
|
|
||||||
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
|
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
|
const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
|
||||||
@ -36,7 +36,7 @@ export const metadata = {
|
|||||||
export default async function RootLayout({ children }: { children: ReactNode }) {
|
export default async function RootLayout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={GeistSans.variable}>
|
<html lang="en" className={GeistSans.variable}>
|
||||||
<body className="min-h-screen bg-white text-black selection:bg-primary-muted dark:bg-neutral-900 dark:text-white dark:selection:bg-primary-emphasis dark:selection:text-white">
|
<body className="min-h-screen bg-white text-black selection:bg-primary-muted">
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
{/* We need to have this wrapper div because the headless ui popover clickaway event is not working properly */}
|
{/* We need to have this wrapper div because the headless ui popover clickaway event is not working properly */}
|
||||||
{/* https://github.com/tailwindlabs/headlessui/issues/2752#issuecomment-1724096430 */}
|
{/* https://github.com/tailwindlabs/headlessui/issues/2752#issuecomment-1724096430 */}
|
||||||
@ -46,7 +46,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|||||||
<Navbar />
|
<Navbar />
|
||||||
</header>
|
</header>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<main className="main group flex-1">{children}</main>
|
<main>{children}</main>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
@ -11,8 +11,6 @@ import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
|||||||
import { getProduct, getProductRecommendations } from 'lib/shopify';
|
import { getProduct, getProductRecommendations } from 'lib/shopify';
|
||||||
import { Image } from 'lib/shopify/types';
|
import { Image } from 'lib/shopify/types';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params
|
params
|
||||||
}: {
|
}: {
|
||||||
|
@ -20,8 +20,6 @@ import ProductsGridPlaceholder from 'components/layout/search/placeholder';
|
|||||||
import SortingMenu from 'components/layout/search/sorting-menu';
|
import SortingMenu from 'components/layout/search/sorting-menu';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params
|
params
|
||||||
}: {
|
}: {
|
||||||
|
@ -4,7 +4,6 @@ import ProductsList from 'components/layout/products-list';
|
|||||||
import { searchProducts } from 'components/layout/products-list/actions';
|
import { searchProducts } from 'components/layout/products-list/actions';
|
||||||
import SortingMenu from 'components/layout/search/sorting-menu';
|
import SortingMenu from 'components/layout/search/sorting-menu';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Search',
|
title: 'Search',
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
'use client';
|
|
||||||
type OrderCardsProps = {
|
|
||||||
orders: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AccountOrdersHistory({ orders }: { orders: any }) {
|
|
||||||
return (
|
|
||||||
<div className="mt-6">
|
|
||||||
<div className="grid w-full gap-4 p-4 py-6 md:gap-8 md:p-8 lg:p-12">
|
|
||||||
<h2 className="text-lead font-bold">Order History</h2>
|
|
||||||
{orders?.length ? <Orders orders={orders} /> : <EmptyOrders />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EmptyOrders() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="mb-1">You haven't placed any orders yet.</div>
|
|
||||||
<div className="w-48">
|
|
||||||
<button
|
|
||||||
className="mt-2 w-full text-sm"
|
|
||||||
//variant="secondary"
|
|
||||||
>
|
|
||||||
Start Shopping
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Orders({ orders }: OrderCardsProps) {
|
|
||||||
return (
|
|
||||||
<ul className="false grid grid-flow-row grid-cols-1 gap-2 gap-y-6 sm:grid-cols-3 md:gap-4 lg:gap-6">
|
|
||||||
{orders.map((order: any) => (
|
|
||||||
<li key={order.node.id}>{order.node.number}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { ArrowRightIcon as LogOutIcon } from '@heroicons/react/24/outline';
|
|
||||||
import { doLogout } from './actions';
|
|
||||||
import LoadingDots from 'components/loading-dots';
|
|
||||||
import { useFormState, useFormStatus } from 'react-dom';
|
|
||||||
|
|
||||||
function SubmitButton(props: any) {
|
|
||||||
const { pending } = useFormStatus();
|
|
||||||
const buttonClasses =
|
|
||||||
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white';
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={(e: React.FormEvent<HTMLButtonElement>) => {
|
|
||||||
if (pending) e.preventDefault();
|
|
||||||
}}
|
|
||||||
aria-label="Log Out"
|
|
||||||
aria-disabled={pending}
|
|
||||||
className={clsx(buttonClasses, {
|
|
||||||
'hover:opacity-90': true,
|
|
||||||
'cursor-not-allowed opacity-60 hover:opacity-60': pending
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="absolute left-0 ml-4">
|
|
||||||
{pending ? <LoadingDots className="mb-3 bg-white" /> : <LogOutIcon className="h-5" />}
|
|
||||||
</div>
|
|
||||||
{pending ? 'Logging out...' : 'Log Out'}
|
|
||||||
</button>
|
|
||||||
{props?.message && <div className="my-5">{props?.message}</div>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AccountProfile() {
|
|
||||||
const [message, formAction] = useFormState(doLogout, null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form action={formAction}>
|
|
||||||
<SubmitButton message={message} />
|
|
||||||
<p aria-live="polite" className="sr-only" role="status">
|
|
||||||
{message}
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
'use server';
|
|
||||||
|
|
||||||
import { CUSTOMER_API_URL, ORIGIN_URL, removeAllCookiesServerAction } from 'lib/shopify/auth';
|
|
||||||
import { redirect } from 'next/navigation';
|
|
||||||
import { cookies } from 'next/headers';
|
|
||||||
|
|
||||||
export async function doLogout() {
|
|
||||||
const origin = ORIGIN_URL;
|
|
||||||
const customerAccountApiUrl = CUSTOMER_API_URL;
|
|
||||||
let logoutUrl;
|
|
||||||
try {
|
|
||||||
const idToken = cookies().get('shop_id_token');
|
|
||||||
const idTokenValue = idToken?.value;
|
|
||||||
if (!idTokenValue) {
|
|
||||||
//you can also throw an error here with page and middleware
|
|
||||||
//throw new Error ("Error No Id Token")
|
|
||||||
//if there is no idToken, then sending to logout url will redirect shopify, so just
|
|
||||||
//redirect to login here and delete cookies (presumably they don't even exist)
|
|
||||||
logoutUrl = new URL(`${origin}/login`);
|
|
||||||
} else {
|
|
||||||
logoutUrl = new URL(
|
|
||||||
`${customerAccountApiUrl}/auth/logout?id_token_hint=${idTokenValue}&post_logout_redirect_uri=${origin}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await removeAllCookiesServerAction();
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Error', e);
|
|
||||||
//you can throw error here or return - return goes back to form b/c of state, throw will throw the error boundary
|
|
||||||
//throw new Error ("Error")
|
|
||||||
return 'Error logging out. Please try again';
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(`${logoutUrl}`); // Navigate to the new post page
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { doLogin } from './actions';
|
|
||||||
import { useFormState, useFormStatus } from 'react-dom';
|
|
||||||
|
|
||||||
function SubmitButton(props: any) {
|
|
||||||
const { pending } = useFormStatus();
|
|
||||||
const buttonClasses =
|
|
||||||
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white';
|
|
||||||
//const disabledClasses = 'cursor-not-allowed opacity-60 hover:opacity-60';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{props?.message && <div className="my-5">{props?.message}</div>}
|
|
||||||
<button
|
|
||||||
onClick={(e: React.FormEvent<HTMLButtonElement>) => {
|
|
||||||
if (pending) e.preventDefault();
|
|
||||||
}}
|
|
||||||
aria-label="Log in"
|
|
||||||
aria-disabled={pending}
|
|
||||||
className={clsx(buttonClasses, {
|
|
||||||
'hover:opacity-90': true,
|
|
||||||
'cursor-not-allowed opacity-60 hover:opacity-60': pending
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{pending ? (
|
|
||||||
<>
|
|
||||||
<span>Logging In...</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span>Log-In</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LoginShopify() {
|
|
||||||
const [message, formAction] = useFormState(doLogin, null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form action={formAction}>
|
|
||||||
<SubmitButton message={message} />
|
|
||||||
<p aria-live="polite" className="sr-only" role="status">
|
|
||||||
{message}
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
export function LoginMessage() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Error</h2>
|
|
||||||
<span>Your session has expired. Please log in again.</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { cookies } from 'next/headers';
|
|
||||||
import { LoginShopify } from 'components/auth/login-form';
|
|
||||||
import { UserIcon } from 'components/auth/user-icon';
|
|
||||||
|
|
||||||
export default async function Login() {
|
|
||||||
const customerToken = cookies().get('shop_customer_token')?.value;
|
|
||||||
const refreshToken = cookies().get('shop_refresh_token')?.value;
|
|
||||||
let isLoggedIn;
|
|
||||||
if (!customerToken && !refreshToken) {
|
|
||||||
isLoggedIn = false;
|
|
||||||
} else {
|
|
||||||
isLoggedIn = true;
|
|
||||||
}
|
|
||||||
console.log('LoggedIn', isLoggedIn);
|
|
||||||
return isLoggedIn ? <UserIcon /> : <LoginShopify />;
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import { UserIcon as User2Icon } from '@heroicons/react/24/outline';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
function UserButton(props: any) {
|
|
||||||
const buttonClasses =
|
|
||||||
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white';
|
|
||||||
//const disabledClasses = 'cursor-not-allowed opacity-60 hover:opacity-60';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
aria-label="My Profile"
|
|
||||||
className={clsx(buttonClasses, {
|
|
||||||
'hover:opacity-90': true
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{/*Purposesly a href here and NOT Link component b/c of router caching*/}
|
|
||||||
<a href="/account">
|
|
||||||
<User2Icon className="mr-2 h-4 w-4" />
|
|
||||||
<span>Profile</span>
|
|
||||||
</a>
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function UserIcon() {
|
|
||||||
return <UserButton />;
|
|
||||||
}
|
|
@ -34,8 +34,7 @@ export async function addItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cart = await addToCart(cartId, selectedVariantIds);
|
await addToCart(cartId, selectedVariantIds);
|
||||||
console.log({ cartLines: cart.lines });
|
|
||||||
revalidateTag(TAGS.cart);
|
revalidateTag(TAGS.cart);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 'Error adding item to cart';
|
return 'Error adding item to cart';
|
||||||
|
@ -6,6 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import LoadingDots from 'components/loading-dots';
|
import LoadingDots from 'components/loading-dots';
|
||||||
import Price from 'components/price';
|
import Price from 'components/price';
|
||||||
|
import useAuth from 'hooks/use-auth';
|
||||||
import type { Cart } from 'lib/shopify/types';
|
import type { Cart } from 'lib/shopify/types';
|
||||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
@ -14,7 +15,12 @@ import CloseCart from './close-cart';
|
|||||||
import LineItem from './line-item';
|
import LineItem from './line-item';
|
||||||
import OpenCart from './open-cart';
|
import OpenCart from './open-cart';
|
||||||
import VehicleDetails, { VehicleFormSchema, vehicleFormSchema } from './vehicle-details';
|
import VehicleDetails, { VehicleFormSchema, vehicleFormSchema } from './vehicle-details';
|
||||||
import useAuth from 'hooks/use-auth';
|
|
||||||
|
const getCheckoutUrlWithAuthentication = (url: string) => {
|
||||||
|
const checkoutUrl = new URL(url);
|
||||||
|
checkoutUrl.searchParams.append('logged_in', 'true');
|
||||||
|
return checkoutUrl.toString();
|
||||||
|
};
|
||||||
|
|
||||||
export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
@ -22,7 +28,6 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
const quantityRef = useRef(cart?.totalQuantity);
|
const quantityRef = useRef(cart?.totalQuantity);
|
||||||
const openCart = () => setIsOpen(true);
|
const openCart = () => setIsOpen(true);
|
||||||
const closeCart = () => setIsOpen(false);
|
const closeCart = () => setIsOpen(false);
|
||||||
const [checkoutUrl, setCheckoutUrl] = useState<string | undefined>(cart?.checkoutUrl);
|
|
||||||
const { control, handleSubmit } = useForm<VehicleFormSchema>({
|
const { control, handleSubmit } = useForm<VehicleFormSchema>({
|
||||||
resolver: zodResolver(vehicleFormSchema),
|
resolver: zodResolver(vehicleFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -48,20 +53,6 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
}
|
}
|
||||||
}, [isOpen, cart?.totalQuantity, quantityRef]);
|
}, [isOpen, cart?.totalQuantity, quantityRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!cart) return;
|
|
||||||
if (isAuthenticated) {
|
|
||||||
const newCheckoutUrl = new URL(cart.checkoutUrl);
|
|
||||||
newCheckoutUrl.searchParams.append('logged_in', 'true');
|
|
||||||
|
|
||||||
return setCheckoutUrl(newCheckoutUrl.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkoutUrl !== cart.checkoutUrl) {
|
|
||||||
setCheckoutUrl(cart.checkoutUrl);
|
|
||||||
}
|
|
||||||
}, [cart, isAuthenticated, checkoutUrl]);
|
|
||||||
|
|
||||||
const onSubmit = async (data: VehicleFormSchema) => {
|
const onSubmit = async (data: VehicleFormSchema) => {
|
||||||
if (!cart) return;
|
if (!cart) return;
|
||||||
|
|
||||||
@ -153,7 +144,15 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href={checkoutUrl} ref={linkRef} className="hidden">
|
<a
|
||||||
|
href={
|
||||||
|
isAuthenticated
|
||||||
|
? getCheckoutUrlWithAuthentication(cart.checkoutUrl)
|
||||||
|
: cart.checkoutUrl
|
||||||
|
}
|
||||||
|
ref={linkRef}
|
||||||
|
className="hidden"
|
||||||
|
>
|
||||||
Proceed to Checkout
|
Proceed to Checkout
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { redirect } from 'next/navigation';
|
|
||||||
import { cookies } from 'next/headers';
|
|
||||||
import {
|
import {
|
||||||
generateCodeVerifier,
|
|
||||||
generateCodeChallenge,
|
|
||||||
generateRandomString,
|
|
||||||
CUSTOMER_API_CLIENT_ID,
|
CUSTOMER_API_CLIENT_ID,
|
||||||
|
CUSTOMER_API_URL,
|
||||||
ORIGIN_URL,
|
ORIGIN_URL,
|
||||||
CUSTOMER_API_URL
|
generateCodeChallenge,
|
||||||
|
generateCodeVerifier,
|
||||||
|
generateRandomString,
|
||||||
|
removeAllCookiesServerAction
|
||||||
} from 'lib/shopify/auth';
|
} from 'lib/shopify/auth';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
export async function doLogin(_: any) {
|
export async function doLogin() {
|
||||||
const customerAccountApiUrl = CUSTOMER_API_URL;
|
const customerAccountApiUrl = CUSTOMER_API_URL;
|
||||||
const clientId = CUSTOMER_API_CLIENT_ID;
|
const clientId = CUSTOMER_API_CLIENT_ID;
|
||||||
const origin = ORIGIN_URL;
|
const origin = ORIGIN_URL;
|
||||||
@ -20,7 +21,7 @@ export async function doLogin(_: any) {
|
|||||||
try {
|
try {
|
||||||
loginUrl.searchParams.set('client_id', clientId);
|
loginUrl.searchParams.set('client_id', clientId);
|
||||||
loginUrl.searchParams.append('response_type', 'code');
|
loginUrl.searchParams.append('response_type', 'code');
|
||||||
loginUrl.searchParams.append('redirect_uri', `${origin}/authorize`);
|
loginUrl.searchParams.append('redirect_uri', `${origin}/api/authorize`);
|
||||||
loginUrl.searchParams.set(
|
loginUrl.searchParams.set(
|
||||||
'scope',
|
'scope',
|
||||||
'openid email https://api.customers.com/auth/customer.graphql'
|
'openid email https://api.customers.com/auth/customer.graphql'
|
||||||
@ -56,3 +57,21 @@ export async function isLoggedIn() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const doLogout = async () => {
|
||||||
|
const idToken = cookies().get('shop_id_token');
|
||||||
|
const idTokenValue = idToken?.value;
|
||||||
|
|
||||||
|
await removeAllCookiesServerAction();
|
||||||
|
//if there is no idToken, then sending to logout url will redirect shopify, so just
|
||||||
|
//redirect to login here and delete cookies (presumably they don't even exist)
|
||||||
|
if (!idTokenValue) {
|
||||||
|
redirect(ORIGIN_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
const logoutUrl = new URL(
|
||||||
|
`${CUSTOMER_API_URL}/auth/logout?id_token_hint=${idTokenValue}&post_logout_redirect_uri=${ORIGIN_URL}`
|
||||||
|
);
|
||||||
|
|
||||||
|
redirect(logoutUrl.toString());
|
||||||
|
};
|
@ -1,26 +1,25 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { CloseButton, Popover, PopoverButton, PopoverPanel, Transition } from '@headlessui/react';
|
import { CloseButton, Popover, PopoverButton, PopoverPanel, Transition } from '@headlessui/react';
|
||||||
import { ArrowRightIcon } from '@heroicons/react/16/solid';
|
import { ArrowRightIcon } from '@heroicons/react/16/solid';
|
||||||
import { Menu } from 'lib/shopify/types';
|
|
||||||
import { Fragment, useState } from 'react';
|
|
||||||
import OpenProfile from './open-profile';
|
|
||||||
import { useFormState, useFormStatus } from 'react-dom';
|
|
||||||
import { doLogin } from 'components/auth/actions';
|
|
||||||
import { Button } from 'components/button';
|
import { Button } from 'components/button';
|
||||||
import useAuth from 'hooks/use-auth';
|
import useAuth from 'hooks/use-auth';
|
||||||
|
import { Menu } from 'lib/shopify/types';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { Fragment } from 'react';
|
||||||
|
import { useFormState, useFormStatus } from 'react-dom';
|
||||||
|
import { doLogin, doLogout } from './actions';
|
||||||
|
import OpenProfile from './open-profile';
|
||||||
|
|
||||||
type ProfilePopoverProps = {
|
type ProfilePopoverProps = {
|
||||||
menu: Menu[];
|
menu: Menu[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function SubmitButton(props: any) {
|
function SignInButton({ message }: { message: string | null }) {
|
||||||
const { pending } = useFormStatus();
|
const { pending } = useFormStatus();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{props?.message && <div className="my-5">{props?.message}</div>}
|
{message && <div className="my-5">{message}</div>}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
aria-label="Log in"
|
aria-label="Log in"
|
||||||
@ -35,11 +34,19 @@ function SubmitButton(props: any) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LogoutButton = () => {
|
||||||
|
const { pending } = useFormStatus();
|
||||||
|
return (
|
||||||
|
<Button disabled={pending} type="submit" variant="outlined" className="w-full">
|
||||||
|
{pending ? 'Logging Out...' : 'Log Out'}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
|
const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
|
||||||
const [message, action] = useFormState(doLogin, null);
|
const [message, action] = useFormState(doLogin, null);
|
||||||
|
const [, logoutAction] = useFormState(doLogout, null);
|
||||||
const { isAuthenticated, loading } = useAuth();
|
const { isAuthenticated, loading } = useAuth();
|
||||||
const [loggingOut, setLoggingOut] = useState(false);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
@ -60,7 +67,7 @@ const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
|
|||||||
<span className="text-sm font-medium">My Account</span>
|
<span className="text-sm font-medium">My Account</span>
|
||||||
{!isAuthenticated && !loading && (
|
{!isAuthenticated && !loading && (
|
||||||
<form action={action}>
|
<form action={action}>
|
||||||
<SubmitButton message={message} />
|
<SignInButton message={message} />
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
{menu.length ? (
|
{menu.length ? (
|
||||||
@ -90,16 +97,9 @@ const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
|
|||||||
</ul>
|
</ul>
|
||||||
) : null}
|
) : null}
|
||||||
{isAuthenticated && !loading && (
|
{isAuthenticated && !loading && (
|
||||||
<Button
|
<form action={logoutAction}>
|
||||||
disabled={loggingOut}
|
<LogoutButton />
|
||||||
onClick={() => {
|
</form>
|
||||||
setLoggingOut(true);
|
|
||||||
router.push('/logout');
|
|
||||||
}}
|
|
||||||
variant="outlined"
|
|
||||||
>
|
|
||||||
{loggingOut ? 'Logging Out...' : 'Log Out'}
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</PopoverPanel>
|
</PopoverPanel>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { isLoggedIn } from 'components/auth/actions';
|
import { isLoggedIn } from 'components/profile/actions';
|
||||||
import { createContext, useState, useEffect } from 'react';
|
import { createContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
type AuthContextType = {
|
type AuthContextType = {
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
|
@ -96,7 +96,7 @@ export async function initialAccessToken(
|
|||||||
const body = new URLSearchParams();
|
const body = new URLSearchParams();
|
||||||
body.append('grant_type', 'authorization_code');
|
body.append('grant_type', 'authorization_code');
|
||||||
body.append('client_id', clientId);
|
body.append('client_id', clientId);
|
||||||
body.append('redirect_uri', `${newOrigin}/authorize`);
|
body.append('redirect_uri', `${newOrigin}/api/authorize`);
|
||||||
body.append('code', code);
|
body.append('code', code);
|
||||||
body.append('code_verifier', codeVerifier?.value);
|
body.append('code_verifier', codeVerifier?.value);
|
||||||
const userAgent = '*';
|
const userAgent = '*';
|
||||||
@ -424,7 +424,7 @@ export async function authorize(request: NextRequest, origin: string) {
|
|||||||
if (!dataInitialToken.success) {
|
if (!dataInitialToken.success) {
|
||||||
console.log('Error: Access Denied. Check logs', dataInitialToken.message);
|
console.log('Error: Access Denied. Check logs', dataInitialToken.message);
|
||||||
newHeaders.set('x-shop-access', 'denied');
|
newHeaders.set('x-shop-access', 'denied');
|
||||||
return NextResponse.next({
|
return NextResponse.json({
|
||||||
request: {
|
request: {
|
||||||
// New request headers
|
// New request headers
|
||||||
headers: newHeaders
|
headers: newHeaders
|
||||||
@ -445,7 +445,7 @@ export async function authorize(request: NextRequest, origin: string) {
|
|||||||
if (!customerAccessToken.success) {
|
if (!customerAccessToken.success) {
|
||||||
console.log('Error: Customer Access Token');
|
console.log('Error: Customer Access Token');
|
||||||
newHeaders.set('x-shop-access', 'denied');
|
newHeaders.set('x-shop-access', 'denied');
|
||||||
return NextResponse.next({
|
return NextResponse.json({
|
||||||
request: {
|
request: {
|
||||||
// New request headers
|
// New request headers
|
||||||
headers: newHeaders
|
headers: newHeaders
|
||||||
@ -483,26 +483,3 @@ export async function authorize(request: NextRequest, origin: string) {
|
|||||||
id_token
|
id_token
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logout(request: NextRequest, origin: string) {
|
|
||||||
//console.log("New Origin", newOrigin)
|
|
||||||
const idToken = request.cookies.get('shop_id_token');
|
|
||||||
const idTokenValue = idToken?.value;
|
|
||||||
//revalidateTag(TAGS.customer); //this causes some strange error in Nextjs about invariant, so removing for now
|
|
||||||
|
|
||||||
//if there is no idToken, then sending to logout url will redirect shopify, so just
|
|
||||||
//redirect to login here and delete cookies (presumably they don't even exist)
|
|
||||||
if (!idTokenValue) {
|
|
||||||
const logoutUrl = new URL(`${origin}`);
|
|
||||||
const response = NextResponse.redirect(`${logoutUrl}`);
|
|
||||||
return removeAllCookies(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log ("id toke value", idTokenValue)
|
|
||||||
const logoutUrl = new URL(
|
|
||||||
`${CUSTOMER_API_URL}/auth/logout?id_token_hint=${idTokenValue}&post_logout_redirect_uri=${origin}`
|
|
||||||
);
|
|
||||||
//console.log ("logout url", logoutUrl)
|
|
||||||
const logoutResponse = NextResponse.redirect(logoutUrl);
|
|
||||||
return removeAllCookies(logoutResponse);
|
|
||||||
}
|
|
||||||
|
@ -1,49 +1,15 @@
|
|||||||
|
import { getOrigin, isLoggedIn } from 'lib/shopify/auth';
|
||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
import { isLoggedIn, getOrigin, authorize, logout } from 'lib/shopify/auth';
|
|
||||||
|
|
||||||
// This function can be marked `async` if using `await` inside
|
// This function can be marked `async` if using `await` inside
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
/****
|
|
||||||
Authorize Middleware to get access tokens
|
|
||||||
*****/
|
|
||||||
if (request.nextUrl.pathname.startsWith('/authorize')) {
|
|
||||||
console.log('Running Initial Authorization Middleware');
|
|
||||||
const origin = getOrigin(request);
|
|
||||||
console.log('origin', origin);
|
|
||||||
return await authorize(request, origin);
|
|
||||||
}
|
|
||||||
/****
|
|
||||||
END OF Authorize Middleware to get access tokens
|
|
||||||
*****/
|
|
||||||
|
|
||||||
/****
|
|
||||||
LOGOUT -
|
|
||||||
*****/
|
|
||||||
if (request.nextUrl.pathname.startsWith('/logout')) {
|
|
||||||
console.log('Running Logout middleware');
|
|
||||||
const origin = getOrigin(request);
|
|
||||||
return await logout(request, origin);
|
|
||||||
}
|
|
||||||
/****
|
|
||||||
END OF LOGOUT
|
|
||||||
*****/
|
|
||||||
/****
|
|
||||||
Account
|
|
||||||
*****/
|
|
||||||
|
|
||||||
if (request.nextUrl.pathname.startsWith('/account')) {
|
if (request.nextUrl.pathname.startsWith('/account')) {
|
||||||
console.log('Running Account middleware');
|
console.log('Running Account middleware');
|
||||||
//const newHeaders = new Headers(request.headers)
|
|
||||||
const origin = getOrigin(request);
|
const origin = getOrigin(request);
|
||||||
//console.log ("origin", origin)
|
|
||||||
return await isLoggedIn(request, origin);
|
return await isLoggedIn(request, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/****
|
|
||||||
END OF Account
|
|
||||||
*****/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ['/authorize', '/logout', '/account/:path*']
|
matcher: ['/account/:path*']
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user