diff --git a/app/(auth)/authorize/page.tsx b/app/(auth)/authorize/page.tsx deleted file mode 100644 index 806e5c47e..000000000 --- a/app/(auth)/authorize/page.tsx +++ /dev/null @@ -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 ( - <> -
-
-
Loading...
-
-
- - ); -} diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx deleted file mode 100644 index d8748e347..000000000 --- a/app/(auth)/login/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { LoginMessage } from 'components/auth/login-message'; - -export const runtime = 'edge'; - -export default async function LoginPage() { - return ( - <> -
-
-
- -
-
-
- - ); -} diff --git a/app/(auth)/logout/page.tsx b/app/(auth)/logout/page.tsx deleted file mode 100644 index 3b7080c25..000000000 --- a/app/(auth)/logout/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export const runtime = 'edge'; - -export default async function LogoutPage() { - return ( - <> -
-
-
Loading...
-
-
- - ); -} diff --git a/app/account/orders/[id]/page.tsx b/app/account/orders/[id]/page.tsx index 51fb47237..634f39754 100644 --- a/app/account/orders/[id]/page.tsx +++ b/app/account/orders/[id]/page.tsx @@ -1,20 +1,17 @@ -import { CheckCircleIcon, TruckIcon, ArrowLeftIcon } from '@heroicons/react/24/outline'; -import Image from 'next/image'; +import { ArrowLeftIcon, CheckCircleIcon, TruckIcon } from '@heroicons/react/24/outline'; 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 Heading from 'components/ui/heading'; import Label from 'components/ui/label'; +import Text from 'components/ui/text'; import { getCustomerOrder } from 'lib/shopify'; import { Fulfillment, Order } from 'lib/shopify/types'; -import Text from 'components/ui/text'; -import Price from 'components/price'; -import Badge from 'components/ui/badge'; +import Image from 'next/image'; 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) { return new Date(date).toLocaleDateString('en-US', { @@ -242,9 +239,7 @@ export default async function OrderPage({ params }: { params: { id: string } }) return ( <> - - - +
diff --git a/app/account/page.tsx b/app/account/page.tsx index c26ed1650..d25dac246 100644 --- a/app/account/page.tsx +++ b/app/account/page.tsx @@ -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 Heading from 'components/ui/heading'; -import Label from 'components/ui/label'; +import Divider from 'components/divider'; +import Price from 'components/price'; import Badge from 'components/ui/badge'; import { Card } from 'components/ui/card'; - -export const runtime = 'edge'; +import Heading from 'components/ui/heading'; +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() { const orders = await getCustomerOrders(); diff --git a/app/api/authorize/route.ts b/app/api/authorize/route.ts new file mode 100644 index 000000000..c889f4fef --- /dev/null +++ b/app/api/authorize/route.ts @@ -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 { + const origin = getOrigin(request); + return await authorize(request, origin); +} diff --git a/app/layout.tsx b/app/layout.tsx index 1080eebcc..63c8b5fe5 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,10 +1,10 @@ import Banner from 'components/banner'; import Navbar from 'components/layout/navbar'; +import { AuthProvider } from 'contexts/auth-context'; import { GeistSans } from 'geist/font/sans'; import { ensureStartsWith } from 'lib/utils'; import { ReactNode, Suspense } from 'react'; import './globals.css'; -import { AuthProvider } from 'contexts/auth-context'; const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env; const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL @@ -36,7 +36,7 @@ export const metadata = { export default async function RootLayout({ children }: { children: ReactNode }) { return ( - + {/* 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 */} @@ -46,7 +46,7 @@ export default async function RootLayout({ children }: { children: ReactNode }) -
{children}
+
{children}
diff --git a/app/product/[handle]/page.tsx b/app/product/[handle]/page.tsx index 29e07422c..52a8a7b3d 100644 --- a/app/product/[handle]/page.tsx +++ b/app/product/[handle]/page.tsx @@ -11,8 +11,6 @@ import { HIDDEN_PRODUCT_TAG } from 'lib/constants'; import { getProduct, getProductRecommendations } from 'lib/shopify'; import { Image } from 'lib/shopify/types'; -export const runtime = 'edge'; - export async function generateMetadata({ params }: { diff --git a/app/search/[collection]/page.tsx b/app/search/[collection]/page.tsx index 4aa9068a5..0909ce882 100644 --- a/app/search/[collection]/page.tsx +++ b/app/search/[collection]/page.tsx @@ -20,8 +20,6 @@ import ProductsGridPlaceholder from 'components/layout/search/placeholder'; import SortingMenu from 'components/layout/search/sorting-menu'; import { Suspense } from 'react'; -export const runtime = 'edge'; - export async function generateMetadata({ params }: { diff --git a/app/search/page.tsx b/app/search/page.tsx index b8ac7df27..320795690 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -4,7 +4,6 @@ import ProductsList from 'components/layout/products-list'; import { searchProducts } from 'components/layout/products-list/actions'; import SortingMenu from 'components/layout/search/sorting-menu'; import { Suspense } from 'react'; -export const runtime = 'edge'; export const metadata = { title: 'Search', diff --git a/components/account/account-orders-history.tsx b/components/account/account-orders-history.tsx deleted file mode 100644 index 6ad8046c3..000000000 --- a/components/account/account-orders-history.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client'; -type OrderCardsProps = { - orders: any; -}; - -export function AccountOrdersHistory({ orders }: { orders: any }) { - return ( -
-
-

Order History

- {orders?.length ? : } -
-
- ); -} - -function EmptyOrders() { - return ( -
-
You haven't placed any orders yet.
-
- -
-
- ); -} - -function Orders({ orders }: OrderCardsProps) { - return ( -
    - {orders.map((order: any) => ( -
  • {order.node.number}
  • - ))} -
- ); -} diff --git a/components/account/account-profile.tsx b/components/account/account-profile.tsx deleted file mode 100644 index 92bc2d867..000000000 --- a/components/account/account-profile.tsx +++ /dev/null @@ -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 ( - <> - - {props?.message &&
{props?.message}
} - - ); -} - -export function AccountProfile() { - const [message, formAction] = useFormState(doLogout, null); - - return ( -
- -

- {message} -

- - ); -} diff --git a/components/account/actions.ts b/components/account/actions.ts deleted file mode 100644 index 3006d8f5a..000000000 --- a/components/account/actions.ts +++ /dev/null @@ -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 -} diff --git a/components/auth/login-form.tsx b/components/auth/login-form.tsx deleted file mode 100644 index 3410b0e84..000000000 --- a/components/auth/login-form.tsx +++ /dev/null @@ -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 &&
{props?.message}
} - - - ); -} - -export function LoginShopify() { - const [message, formAction] = useFormState(doLogin, null); - - return ( -
- -

- {message} -

- - ); -} diff --git a/components/auth/login-message.tsx b/components/auth/login-message.tsx deleted file mode 100644 index f32d8bd1c..000000000 --- a/components/auth/login-message.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export function LoginMessage() { - return ( -
-

Error

- Your session has expired. Please log in again. -
- ); -} diff --git a/components/auth/login.tsx b/components/auth/login.tsx deleted file mode 100644 index f63123e45..000000000 --- a/components/auth/login.tsx +++ /dev/null @@ -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 ? : ; -} diff --git a/components/auth/user-icon.tsx b/components/auth/user-icon.tsx deleted file mode 100644 index fa39fdece..000000000 --- a/components/auth/user-icon.tsx +++ /dev/null @@ -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 ( - <> - - - ); -} - -export function UserIcon() { - return ; -} diff --git a/components/cart/actions.ts b/components/cart/actions.ts index 6bc81e3b6..31bfa2273 100644 --- a/components/cart/actions.ts +++ b/components/cart/actions.ts @@ -34,8 +34,7 @@ export async function addItem( } try { - const cart = await addToCart(cartId, selectedVariantIds); - console.log({ cartLines: cart.lines }); + await addToCart(cartId, selectedVariantIds); revalidateTag(TAGS.cart); } catch (e) { return 'Error adding item to cart'; diff --git a/components/cart/modal.tsx b/components/cart/modal.tsx index 2e55b3507..3bd45bc91 100644 --- a/components/cart/modal.tsx +++ b/components/cart/modal.tsx @@ -6,6 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import clsx from 'clsx'; import LoadingDots from 'components/loading-dots'; import Price from 'components/price'; +import useAuth from 'hooks/use-auth'; import type { Cart } from 'lib/shopify/types'; import { Fragment, useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; @@ -14,7 +15,12 @@ import CloseCart from './close-cart'; import LineItem from './line-item'; import OpenCart from './open-cart'; 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 }) { const { isAuthenticated } = useAuth(); @@ -22,7 +28,6 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) { const quantityRef = useRef(cart?.totalQuantity); const openCart = () => setIsOpen(true); const closeCart = () => setIsOpen(false); - const [checkoutUrl, setCheckoutUrl] = useState(cart?.checkoutUrl); const { control, handleSubmit } = useForm({ resolver: zodResolver(vehicleFormSchema), defaultValues: { @@ -48,20 +53,6 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) { } }, [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) => { if (!cart) return; @@ -153,7 +144,15 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) { />
- + Proceed to Checkout + ); +}; const ProfilePopover = ({ menu }: ProfilePopoverProps) => { const [message, action] = useFormState(doLogin, null); + const [, logoutAction] = useFormState(doLogout, null); const { isAuthenticated, loading } = useAuth(); - const [loggingOut, setLoggingOut] = useState(false); - const router = useRouter(); return ( @@ -60,7 +67,7 @@ const ProfilePopover = ({ menu }: ProfilePopoverProps) => { My Account {!isAuthenticated && !loading && (
- + )} {menu.length ? ( @@ -90,16 +97,9 @@ const ProfilePopover = ({ menu }: ProfilePopoverProps) => { ) : null} {isAuthenticated && !loading && ( - +
+ + )} diff --git a/contexts/auth-context.tsx b/contexts/auth-context.tsx index 0034a0b21..691e051df 100644 --- a/contexts/auth-context.tsx +++ b/contexts/auth-context.tsx @@ -1,6 +1,6 @@ 'use client'; -import { isLoggedIn } from 'components/auth/actions'; -import { createContext, useState, useEffect } from 'react'; +import { isLoggedIn } from 'components/profile/actions'; +import { createContext, useEffect, useState } from 'react'; type AuthContextType = { isAuthenticated: boolean; diff --git a/lib/shopify/auth.ts b/lib/shopify/auth.ts index 37dcfca58..f0a82a52e 100644 --- a/lib/shopify/auth.ts +++ b/lib/shopify/auth.ts @@ -96,7 +96,7 @@ export async function initialAccessToken( const body = new URLSearchParams(); body.append('grant_type', 'authorization_code'); 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_verifier', codeVerifier?.value); const userAgent = '*'; @@ -424,7 +424,7 @@ export async function authorize(request: NextRequest, origin: string) { if (!dataInitialToken.success) { console.log('Error: Access Denied. Check logs', dataInitialToken.message); newHeaders.set('x-shop-access', 'denied'); - return NextResponse.next({ + return NextResponse.json({ request: { // New request headers headers: newHeaders @@ -445,7 +445,7 @@ export async function authorize(request: NextRequest, origin: string) { if (!customerAccessToken.success) { console.log('Error: Customer Access Token'); newHeaders.set('x-shop-access', 'denied'); - return NextResponse.next({ + return NextResponse.json({ request: { // New request headers headers: newHeaders @@ -483,26 +483,3 @@ export async function authorize(request: NextRequest, origin: string) { 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); -} diff --git a/middleware.ts b/middleware.ts index 7352974b0..032b2ae80 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,49 +1,15 @@ +import { getOrigin, isLoggedIn } from 'lib/shopify/auth'; 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 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')) { console.log('Running Account middleware'); - //const newHeaders = new Headers(request.headers) const origin = getOrigin(request); - //console.log ("origin", origin) return await isLoggedIn(request, origin); } - - /**** - END OF Account - *****/ } export const config = { - matcher: ['/authorize', '/logout', '/account/:path*'] + matcher: ['/account/:path*'] };