Skip to content

Commit

Permalink
Merge pull request #3 from khanghy2130/add-auth
Browse files Browse the repository at this point in the history
added authentication
  • Loading branch information
khanghy2130 authored Jan 16, 2024
2 parents ea24dac + f86c681 commit 271ddee
Show file tree
Hide file tree
Showing 23 changed files with 1,682 additions and 620 deletions.
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

0 comments on commit 271ddee

Please sign in to comment.