Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

added authentication #3

Merged
merged 14 commits into from
Jan 16, 2024
Merged
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ yarn install
yarn dev
```

## Run tests
```sh
yarn test
```

## Generate DB types
Replace `abcd12345` with project ID.
```sh
npx supabase gen types typescript --project-id abcd12345 > database.types.ts
```

## Deployment

```sh
Expand Down
Binary file added app/assets/oauth_providers/github-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/oauth_providers/google-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/title_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions app/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect, useState, useRef } from 'react'
import { Link } from "@remix-run/react";

import titleLogoImage from "~/assets/title_logo.png"
import useScrollBehavior from '../utils/Navbar/useScrollBehavior';


type Props = {
setSidePanelIsShown: React.Dispatch<React.SetStateAction<boolean>>;
};

export default function Navbar(
{setSidePanelIsShown}
: Props){
const {navIsShown} = useScrollBehavior();

return <div className={(navIsShown ? "top-0" : "-top-32") +
' fixed transition-[top] ease-in-out duration-500 w-screen bg-red-800'}>
<div className='
flex flex-row justify-between
max-w-screen-lg mx-auto h-auto
'>
<Link to="/">
<img src={titleLogoImage}
className='w-40' />
</Link>
<div>
<Link to="/profile">
<button className='btn'>Profile</button>
</Link>
<Link to="/store">
<button className='btn'>Store</button>
</Link>
<Link to="/cart">
<button className='btn'>Cart</button>
</Link>
<button className='btn' onClick={()=>setSidePanelIsShown(true)}>
More
</button>
</div>

</div>
</div>
}
45 changes: 45 additions & 0 deletions app/components/SidePanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Link } from '@remix-run/react';

import { useTheme } from '~/utils/Navbar/ThemeProvider';

import type { ContextProps } from '../utils/types/ContextProps.type';


type Props = {
sidePanelIsShown: boolean;
setSidePanelIsShown: React.Dispatch<React.SetStateAction<boolean>>;
user: ContextProps["user"];
supabase: ContextProps["supabase"]
};

export default function SidePanel(
{sidePanelIsShown, setSidePanelIsShown, user, supabase}
: Props){
const [theme, setTheme] = useTheme();
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
};

const logout = async () => {
const {error} = await supabase.auth.signOut();
if (error) alert("Error while logging out.");
}

return <div
className={(sidePanelIsShown ? "right-0" : "-right-96") +
` fixed h-screen transition-[right] ease-in-out duration-500
w-96 max-w-full bg-gray-800 flex flex-col items-center`
}>
<button className='btn'
onClick={()=>setSidePanelIsShown(false)}>CLOSE</button>
<button className='btn' onClick={toggleTheme}>Theme: {theme}</button>

{user? <div className='flex flex-col mt-12'>
<p>Logged in as {user.email}</p>
<button className='btn' onClick={logout}>Log out</button>
</div> : <Link to="/login">
<button className='btn'>Login</button>
</Link>}

</div>
}
24 changes: 24 additions & 0 deletions app/components/SpinnerSVG.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

type Props = {
size: number;
};

export default function SidePanel({size} : Props){
return <svg className={`h-${size} w-${size}`} viewBox="0 0 24 24">
<rect width="6" height="14" x="1" y="4" fill="currentColor">
<animate id="svgSpinnersBarsScaleFade0" fill="freeze" attributeName="y" begin="0;svgSpinnersBarsScaleFade1.end-0.25s" dur="0.75s" values="1;5"/>
<animate fill="freeze" attributeName="height" begin="0;svgSpinnersBarsScaleFade1.end-0.25s" dur="0.75s" values="22;14"/>
<animate fill="freeze" attributeName="opacity" begin="0;svgSpinnersBarsScaleFade1.end-0.25s" dur="0.75s" values="1;.2"/>
</rect>
<rect width="6" height="14" x="9" y="4" fill="currentColor" opacity=".4">
<animate fill="freeze" attributeName="y" begin="svgSpinnersBarsScaleFade0.begin+0.15s" dur="0.75s" values="1;5"/>
<animate fill="freeze" attributeName="height" begin="svgSpinnersBarsScaleFade0.begin+0.15s" dur="0.75s" values="22;14"/>
<animate fill="freeze" attributeName="opacity" begin="svgSpinnersBarsScaleFade0.begin+0.15s" dur="0.75s" values="1;.2"/>
</rect>
<rect width="6" height="14" x="17" y="4" fill="currentColor" opacity=".3">
<animate id="svgSpinnersBarsScaleFade1" fill="freeze" attributeName="y" begin="svgSpinnersBarsScaleFade0.begin+0.3s" dur="0.75s" values="1;5"/>
<animate fill="freeze" attributeName="height" begin="svgSpinnersBarsScaleFade0.begin+0.3s" dur="0.75s" values="22;14"/>
<animate fill="freeze" attributeName="opacity" begin="svgSpinnersBarsScaleFade0.begin+0.3s" dur="0.75s" values="1;.2"/>
</rect>
</svg>;
}
164 changes: 110 additions & 54 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,126 @@

import type { LinksFunction } from "@remix-run/node";
import type { Database } from '../database.types'
import type { LinksFunction, LoaderFunction } from "@remix-run/node";
import type { User } from "@supabase/supabase-js";
import { Database } from "database.types";

import { json } from "@remix-run/node";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
useRevalidator
Links,
Meta,
Outlet,
Scripts,
useLoaderData,
useRevalidator
} from "@remix-run/react";
import { useState, useEffect } from "react";
import { createBrowserClient } from '@supabase/auth-helpers-remix'
import { createBrowserClient } from '@supabase/ssr'
import { useLocation } from "@remix-run/react";


import stylesheet from "~/tailwind.css";
import { ThemeProvider, useTheme, ThemeType } from '~/utils/Navbar/ThemeProvider';
import { getThemeSession } from './utils/Navbar/theme.server';


import Navbar from "./components/Navbar";
import SidePanel from "./components/SidePanel";




export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
{ rel: "stylesheet", href: stylesheet },
];

export async function loader(){
const env = {
SUPABASE_URL: process.env.SUPABASE_URL!,
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!,
}
return json({ env })
export type LoaderData = {
theme: ThemeType | null;
env: { [key: string]: string }
};

export const loader: LoaderFunction = async ({ request }) => {
const themeSession = await getThemeSession(request);

const data: LoaderData = {
theme: themeSession.getTheme(),
env: {
SUPABASE_URL: process.env.SUPABASE_URL!,
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!,
}
};

return json(data)
}


export default function App() {
const { env } = useLoaderData<typeof loader>()
const [supabase] = useState(() =>
createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
)

const revalidator = useRevalidator()
// recalling loaders when authentication state changes
useEffect(() => {
const {
data: { subscription }
} = supabase.auth.onAuthStateChange((event, session) => {
revalidator.revalidate()
})

return () => {
subscription.unsubscribe()
}
}, [supabase, revalidator])

return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet context={{ supabase }} />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
function App() {
const { env } = useLoaderData<LoaderFunction>()
const [supabase] = useState(() =>
createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
)
const [user, setUser] = useState<undefined | User>(undefined);

// recalls loaders when authentication state changes
const revalidator = useRevalidator();
useEffect(() => {
const {
data: { subscription }
} = supabase.auth.onAuthStateChange((event, session) => {
revalidator.revalidate();
if (event === "SIGNED_IN") {
setUser(session?.user);
} else if (event === "SIGNED_OUT"){
setUser(undefined);
}
})

return () => {
subscription.unsubscribe()
}
}, [supabase /*, revalidator*/])


const [theme] = useTheme();
const [sidePanelIsShown, setSidePanelIsShown] = useState<boolean>(false);


// hide for specific routes
const location = useLocation();
const routesToHideNavigation = ['/login', '/signup']; ///// add homepage
const shouldHideNavigation = routesToHideNavigation.includes(location.pathname);



return (
<html lang="en" className={theme ?? ""}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{shouldHideNavigation ? null : <>
<Navbar setSidePanelIsShown={setSidePanelIsShown} />
<SidePanel user={user} supabase={supabase}
sidePanelIsShown={sidePanelIsShown}
setSidePanelIsShown={setSidePanelIsShown} />
</>}

{/* space for navbar above main page content */}
<div className="pt-24">
<Outlet context={{ supabase, user }} />
</div>
<Scripts />
</body>
</html>
);
}

export default function AppWithProviders() {
const data = useLoaderData<LoaderData>();
return (
<ThemeProvider specifiedTheme={data.theme}>
<App />
</ThemeProvider>
);
}
7 changes: 0 additions & 7 deletions app/routes/_auth.login.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions app/routes/_auth.signup.tsx

This file was deleted.

9 changes: 0 additions & 9 deletions app/routes/_auth.tsx

This file was deleted.

17 changes: 16 additions & 1 deletion app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,23 @@ export default function Index() {

return (
<div>
<h1 className="text-5xl hover:line-through"
<h1 className="
text-5xl
hover:line-through
text-color-2
"
onClick={headingClicked}>{isDone ? "done" : "click here"}</h1>



{
Array.from(Array(50)).map(
(item, index) => (
<p key={index}>{index}</p>
)
)
}

</div>
);
}
24 changes: 24 additions & 0 deletions app/routes/action.set-theme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { json, redirect } from '@remix-run/node';
import type { ActionFunction, LoaderFunction } from '@remix-run/node';

import { getThemeSession } from '~/utils/Navbar/theme.server';
import { isTheme } from '~/utils/Navbar/ThemeProvider';

export const action: ActionFunction = async ({ request }) => {
const themeSession = await getThemeSession(request);
const requestText = await request.text();
const form = new URLSearchParams(requestText);
const theme = form.get('theme'); // search for theme value

if (!isTheme(theme)) {
return json({
success: false,
message: `theme value of ${theme} is not a valid theme`,
});
}

themeSession.setTheme(theme);
return json({ success: true }, { headers: { 'Set-Cookie': await themeSession.commit() } });
};

export const loader: LoaderFunction = () => redirect('/', { status: 404 });
Loading