From 19220261ed515a5263813888336d0c1b29754c62 Mon Sep 17 00:00:00 2001 From: Joshua Omobola Date: Mon, 23 Dec 2024 15:28:33 +0100 Subject: [PATCH 1/3] feat: update next-app-dir boilerplate --- boilerplate/fullstack/next-app-dir/README.md | 78 +++-- .../app/auth/[[...path]]/page.tsx | 6 +- .../next-app-dir/app/components/dashboard.tsx | 68 +++++ .../next-app-dir/app/components/footer.tsx | 58 ++++ .../next-app-dir/app/components/home.tsx | 82 ++--- .../app/components/linksComponent.tsx | 64 ---- .../{callApiButton.tsx => sessionInfo.tsx} | 9 +- .../next-app-dir/app/config/frontend.tsx | 5 + .../next-app-dir/app/dashboard/page.tsx | 5 + .../fullstack/next-app-dir/app/globals.css | 283 +++++++++++++++++- .../fullstack/next-app-dir/app/layout.tsx | 25 +- .../next-app-dir/app/page.module.css | 214 ------------- .../fullstack/next-app-dir/app/page.tsx | 7 +- .../fullstack/next-app-dir/public/ST.svg | 17 ++ 14 files changed, 542 insertions(+), 379 deletions(-) create mode 100644 boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx create mode 100644 boilerplate/fullstack/next-app-dir/app/components/footer.tsx delete mode 100644 boilerplate/fullstack/next-app-dir/app/components/linksComponent.tsx rename boilerplate/fullstack/next-app-dir/app/components/{callApiButton.tsx => sessionInfo.tsx} (62%) create mode 100644 boilerplate/fullstack/next-app-dir/app/dashboard/page.tsx delete mode 100644 boilerplate/fullstack/next-app-dir/app/page.module.css create mode 100644 boilerplate/fullstack/next-app-dir/public/ST.svg diff --git a/boilerplate/fullstack/next-app-dir/README.md b/boilerplate/fullstack/next-app-dir/README.md index f552de67..f4490523 100644 --- a/boilerplate/fullstack/next-app-dir/README.md +++ b/boilerplate/fullstack/next-app-dir/README.md @@ -1,42 +1,74 @@ -# SuperTokens App with Next.js app directory +# SuperTokens + Next -This is a simple application that is protected by SuperTokens. This app uses the Next.js app directory. +A demo implementation of [SuperTokens](https://supertokens.com/) with [Next.js](https://nextjs.org/). -## How to use +## General Info -### Using `create-next-app` +This project aims to demonstrate how to integrate SuperTokens into a Nexjs application. Its primary purpose is to serve as an educational tool, but it can also be used as a starting point for your own project. -- Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example: +### Config -```bash -npx create-next-app --example with-supertokens with-supertokens-app -``` +#### SuperTokens -```bash -yarn create next-app --example with-supertokens with-supertokens-app -``` +The full configuration needed for SuperTokens (the frontend part) to work is in the `src/config.tsx` file. This file will differ based on the [auth recipe](https://supertokens.com/docs/guides) you choose. -```bash -pnpm create next-app --example with-supertokens with-supertokens-app -``` +If you choose to use this as a starting point for your own project, you can further customize the options and config in the `src/config.tsx` file. Refer to our [docs](https://supertokens.com/docs) (and make sure to choose the correct recipe) for more details. -- Run `yarn install` +## Application Flow -- Run `npm run dev` to start the application on `http://localhost:3000`. +The application consists of four main parts: -### Using `create-supertokens-app` +1. **Root Component (`layout.tsx`)** -- Run the following command + - Initializes SuperTokens with the provided configuration (generated by the CLI) + - Wraps the application with necessary providers: + - `SuperTokensWrapper`: Manages auth state and session + - `ComponentWrapper`: Provides auth UI customization for specific auth recipes. For example, if you choose to use the `ThirdPartyPasswordless` recipe, the `ComponentWrapper` will provide the UI customization for the passwordless login flow (showing a disclaimer about the SMS delivery in demo apps). -```bash -npx create-supertokens-app@latest --frontend=next -``` +2. **Home Component (`/` route, `/page.tsx` component)** -- Select the option to use the app directory + - Public landing page accessible to all users + - Provides navigation to authentication and dashboard + - Displays basic application information and links -Follow the instructions after `create-supertokens-app` has finished +3. **Dashboard Component (`/dashboard` route, `/Dashboard/page.tsx` component)** + - Protected route only accessible to authenticated users + - Protected by `SessionAuth` component + - Displays user information and session details + - Provides functionality to: + - View user ID + - Call test API endpoints + - Access documentation + - Sign out + +When a user visits the application, they start at the home page (`/`). They can choose to authenticate through the `/auth` route, and once authenticated, they gain access to the protected dashboard. The session state is managed throughout the application using SuperTokens' session management. + +## Customizations + +If you want to customize the default auth UI, you have two options: + +1. Refer to the [docs](https://supertokens.com/docs/thirdpartyemailpassword/advanced-customizations/react-component-override/usage) on how to customize the pre-built UI. +2. Roll your own UI by choosing "Custom UI" in the right sidebar in the [docs](https://supertokens.com/docs/thirdpartyemailpassword/quickstart/frontend-setup). + +## Additional resources + +- Custom UI Example: https://github.com/supertokens/supertokens-web-js/tree/master/examples/react/with-thirdpartyemailpassword +- Custom UI Blog post: https://supertokens.medium.com/adding-social-login-to-your-website-with-supertokens-custom-ui-only-5fa4d7ab6402 +- Awesome SuperTokens: https://github.com/kohasummons/awesome-supertokens + +## Contributing + +Please refer to the [CONTRIBUTING.md](https://github.com/supertokens/create-supertokens-app/blob/master/CONTRIBUTING.md) file in the root of the [`create-supertokens-app`](https://github.com/supertokens/create-supertokens-app) repo. + +## Contact us + +For any questions, or support requests, please email us at team@supertokens.io, or join our [Discord](https://supertokens.io/discord) server. ## Notes - To know more about how this app works and to learn how to customise it based on your use cases refer to the [SuperTokens Documentation](https://supertokens.com/docs/guides) - We have provided development OAuth keys for the various built-in third party providers in the `/app/config/backend.ts` file. Feel free to use them for development purposes, but **please create your own keys for production use**. + +## Authors + +Created with :heart: by the folks at SuperTokens.io. diff --git a/boilerplate/fullstack/next-app-dir/app/auth/[[...path]]/page.tsx b/boilerplate/fullstack/next-app-dir/app/auth/[[...path]]/page.tsx index 24d2d28f..f2f8d601 100644 --- a/boilerplate/fullstack/next-app-dir/app/auth/[[...path]]/page.tsx +++ b/boilerplate/fullstack/next-app-dir/app/auth/[[...path]]/page.tsx @@ -17,7 +17,11 @@ export default function Auth() { }, []); if (loaded) { - return SuperTokens.getRoutingComponent(PreBuiltUIList); + return ( +
+ {SuperTokens.getRoutingComponent(PreBuiltUIList)} +
+ ); } return null; diff --git a/boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx b/boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx new file mode 100644 index 00000000..b0db9ea5 --- /dev/null +++ b/boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx @@ -0,0 +1,68 @@ +import { cookies } from "next/headers"; +import { TryRefreshComponent } from "./tryRefreshClientComponent"; +import { redirect } from "next/navigation"; +import { CelebrateIcon } from "../../assets/images"; +import Image from "next/image"; +import { SessionAuthForNextJS } from "./sessionAuthForNextJS"; +import { getSessionForSSR } from "../util"; + +import SessionInfo from "./sessionInfo"; +import Footer from "./footer"; + +export async function DashboardPage() { + const cookiesFromReq = await cookies(); + const cookiesArray: Array<{ name: string; value: string }> = Array.from(cookiesFromReq.getAll()).map( + ({ name, value }) => ({ + name, + value, + }) + ); + const { accessTokenPayload, hasToken, error } = await getSessionForSSR(cookiesArray); + + if (error) { + return
Something went wrong while trying to get the session. Error - {error.message}
; + } + + // `accessTokenPayload` will be undefined if it the session does not exist or has expired + if (accessTokenPayload === undefined) { + if (!hasToken) { + /** + * This means that the user is not logged in. If you want to display some other UI in this + * case, you can do so here. + */ + return redirect("/auth"); + } + + /** + * This means that the session does not exist but we have session tokens for the user. In this case + * the `TryRefreshComponent` will try to refresh the session. + * + * To learn about why the 'key' attribute is required refer to: https://github.com/supertokens/supertokens-node/issues/826#issuecomment-2092144048 + */ + return ; + } + + /** + * SessionAuthForNextJS will handle proper redirection for the user based on the different session states. + * It will redirect to the login page if the session does not exist etc. + */ + return ( + +
+
+
+ Login successful Login successful +
+
+
Your userID is:
+
+ {accessTokenPayload.sub} +
+ +
+
+
+
+
+ ); +} diff --git a/boilerplate/fullstack/next-app-dir/app/components/footer.tsx b/boilerplate/fullstack/next-app-dir/app/components/footer.tsx new file mode 100644 index 00000000..612288ae --- /dev/null +++ b/boilerplate/fullstack/next-app-dir/app/components/footer.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { BlogsIcon, GuideIcon, SeparatorLine, SignOutIcon } from "../../assets/images"; +import Image from "next/image"; +import { recipeDetails } from "../config/frontend"; +import Session from "supertokens-auth-react/recipe/session/index.js"; +import SuperTokens from "supertokens-auth-react"; + +export interface Link { + name: string; + onClick: () => void; + icon: string; +} + +function openLink(url: string) { + window.open(url, "_blank"); +} + +async function logoutClicked() { + await Session.signOut(); + SuperTokens.redirectToAuth(); +} + +const links: Link[] = [ + { + name: "Blogs", + onClick: () => openLink("https://supertokens.com/blog"), + icon: BlogsIcon, + }, + { + name: "Documentation", + onClick: () => openLink(recipeDetails.docsLink), + icon: GuideIcon, + }, + { + name: "Sign Out", + onClick: logoutClicked, + icon: SignOutIcon, + }, +]; + +export default function Footer() { + return ( + <> +
+ {links.map((link) => ( +
+ {link.name} +
+ {link.name} +
+
+ ))} +
+ separator + + ); +} diff --git a/boilerplate/fullstack/next-app-dir/app/components/home.tsx b/boilerplate/fullstack/next-app-dir/app/components/home.tsx index 59b233aa..af2f2a52 100644 --- a/boilerplate/fullstack/next-app-dir/app/components/home.tsx +++ b/boilerplate/fullstack/next-app-dir/app/components/home.tsx @@ -1,68 +1,28 @@ -import { cookies } from "next/headers"; -import { TryRefreshComponent } from "./tryRefreshClientComponent"; -import styles from "../page.module.css"; -import { redirect } from "next/navigation"; -import Image from "next/image"; -import { CelebrateIcon, SeparatorLine } from "../../assets/images"; -import { CallAPIButton } from "./callApiButton"; -import { LinksComponent } from "./linksComponent"; -import { SessionAuthForNextJS } from "./sessionAuthForNextJS"; -import { getSessionForSSR } from "../util"; +import Link from "next/link"; -export async function HomePage() { - const cookiesFromReq = await cookies(); - const cookiesArray: Array<{ name: string; value: string }> = Array.from(cookiesFromReq.getAll()).map( - ({ name, value }) => ({ - name, - value, - }) - ); - const { accessTokenPayload, hasToken, error } = await getSessionForSSR(cookiesArray); - - if (error) { - return
Something went wrong while trying to get the session. Error - {error.message}
; - } - - // `accessTokenPayload` will be undefined if it the session does not exist or has expired - if (accessTokenPayload === undefined) { - if (!hasToken) { - /** - * This means that the user is not logged in. If you want to display some other UI in this - * case, you can do so here. - */ - return redirect("/auth"); - } - - /** - * This means that the session does not exist but we have session tokens for the user. In this case - * the `TryRefreshComponent` will try to refresh the session. - * - * To learn about why the 'key' attribute is required refer to: https://github.com/supertokens/supertokens-node/issues/826#issuecomment-2092144048 - */ - return ; - } - - /** - * SessionAuthForNextJS will handle proper redirection for the user based on the different session states. - * It will redirect to the login page if the session does not exist etc. - */ +export function HomePage() { return ( - -
-
-
- Login successful Login - successful -
-
-
Your userID is:
-
{accessTokenPayload.sub}
- +
+
+ SuperTokens + x + Next.js +
+
+
+

+ SuperTokens x Nextjs
example project +

+
+ + Sign-up / Login + + + Dashboard +
- - separator
- +
); } diff --git a/boilerplate/fullstack/next-app-dir/app/components/linksComponent.tsx b/boilerplate/fullstack/next-app-dir/app/components/linksComponent.tsx deleted file mode 100644 index 2c29291d..00000000 --- a/boilerplate/fullstack/next-app-dir/app/components/linksComponent.tsx +++ /dev/null @@ -1,64 +0,0 @@ -"use client"; -import styles from "../page.module.css"; -import { BlogsIcon, GuideIcon, SignOutIcon } from "../../assets/images"; -import { recipeDetails } from "../config/frontend"; -import Link from "next/link"; -import Image from "next/image"; -import Session from "supertokens-auth-react/recipe/session"; -import SuperTokens from "supertokens-auth-react"; - -const SignOutLink = (props: { name: string; link: string; icon: string }) => { - return ( -
{ - await Session.signOut(); - SuperTokens.redirectToAuth(); - }} - > - {props.name} -
{props.name}
-
- ); -}; - -export const LinksComponent = () => { - const links: { - name: string; - link: string; - icon: string; - }[] = [ - { - name: "Blogs", - link: "https://supertokens.com/blog", - icon: BlogsIcon, - }, - { - name: "Guides", - link: recipeDetails.docsLink, - icon: GuideIcon, - }, - { - name: "Sign Out", - link: "", - icon: SignOutIcon, - }, - ]; - - return ( -
- {links.map((link) => { - if (link.name === "Sign Out") { - return ; - } - - return ( - - {link.name} -
{link.name}
- - ); - })} -
- ); -}; diff --git a/boilerplate/fullstack/next-app-dir/app/components/callApiButton.tsx b/boilerplate/fullstack/next-app-dir/app/components/sessionInfo.tsx similarity index 62% rename from boilerplate/fullstack/next-app-dir/app/components/callApiButton.tsx rename to boilerplate/fullstack/next-app-dir/app/components/sessionInfo.tsx index cd3aa30b..4f4f19d3 100644 --- a/boilerplate/fullstack/next-app-dir/app/components/callApiButton.tsx +++ b/boilerplate/fullstack/next-app-dir/app/components/sessionInfo.tsx @@ -1,17 +1,14 @@ "use client"; -import styles from "../page.module.css"; - -export const CallAPIButton = () => { +export default function SessionInfo() { const fetchUserData = async () => { const userInfoResponse = await fetch("http://localhost:3000/api/user"); - alert(JSON.stringify(await userInfoResponse.json())); }; return ( -
+
fetchUserData()} className="sessionButton"> Call API
); -}; +} diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend.tsx index b799df72..b9daeac3 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend.tsx @@ -24,6 +24,11 @@ export const frontendConfig = (): SuperTokensConfig => { }, }; }, + getRedirectionURL: async (context) => { + if (context.action === "SUCCESS" && context.newSessionCreated) { + return "/dashboard"; + } + }, }; }; diff --git a/boilerplate/fullstack/next-app-dir/app/dashboard/page.tsx b/boilerplate/fullstack/next-app-dir/app/dashboard/page.tsx new file mode 100644 index 00000000..c81a69a4 --- /dev/null +++ b/boilerplate/fullstack/next-app-dir/app/dashboard/page.tsx @@ -0,0 +1,5 @@ +import { DashboardPage } from "../components/dashboard"; + +export default function Dashboard() { + return ; +} diff --git a/boilerplate/fullstack/next-app-dir/app/globals.css b/boilerplate/fullstack/next-app-dir/app/globals.css index 34d61c0f..03749fa6 100644 --- a/boilerplate/fullstack/next-app-dir/app/globals.css +++ b/boilerplate/fullstack/next-app-dir/app/globals.css @@ -1,3 +1,284 @@ -html { +@font-face { + font-family: Menlo; + src: url("../assets/fonts/MenloRegular.ttf"); +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} + +.logos { + display: flex; + align-items: center; + gap: 20px; + padding-bottom: 80px; +} + +.logos span { + font-size: 8rem; + font-weight: bold; +} + +.logos img { + width: 240px; +} + +.App { + display: flex; + flex-direction: column; + width: 100%; height: 100%; + font-family: Rubik; +} + +.fill { + display: flex; + flex-direction: column; + justify-content: center; + flex: 1; +} + +.buttons { + display: flex; + gap: 4px; +} + +.sessionButton { + padding-left: 13px; + padding-right: 13px; + padding-top: 8px; + padding-bottom: 8px; + background-color: black; + border-radius: 10px; + cursor: pointer; + color: white; + font-weight: bold; + font-size: 17px; + text-decoration: none; +} + +header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + padding: 8px; + border-bottom: 2px solid #e0e0e0; +} + +.header-container { + height: 100%; + display: flex; + align-items: center; +} + +.header-container-right { + display: flex; + gap: 12px; +} + +.header-container-right a { + color: #ff8a15; + font-weight: 600; + + &:hover { + color: #ff9933; + } +} + +.header-container img { + width: 40px; +} + +.app-container { + font-family: Rubik, sans-serif; +} + +.app-container * { + box-sizing: border-box; +} + +.bold-400 { + font-variation-settings: "wght" 400; +} + +.bold-500 { + font-variation-settings: "wght" 500; +} + +.bold-600 { + font-variation-settings: "wght" 600; +} + +#home-container { + align-items: center; + min-height: calc(100vh - 58px); + background: url("../assets/images/background.png"); + background-size: cover; +} + +.bold-700 { + font-variation-settings: "wght" 700; +} + +.app-container .main-container { + box-shadow: 0px 0px 60px 0px rgba(0, 0, 0, 0.16); + width: min(635px, calc(100% - 24px)); + border-radius: 16px; + margin-block-end: 159px; + background-color: #ffffff; +} + +.main-container .success-title { + line-height: 1; + padding-block: 26px; + background-color: #e7ffed; + text-align: center; + color: #3eb655; + display: flex; + justify-content: center; + align-items: flex-end; + font-size: 20px; +} + +.success-title img.success-icon { + margin-right: 8px; +} + +.main-container .inner-content { + padding-block: 48px; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.inner-content #user-id { + position: relative; + padding: 14px 17px; + border-image-slice: 1; + width: min(430px, calc(100% - 30px)); + margin-inline: auto; + margin-block: 11px 23px; + border-radius: 9px; + line-height: 1; + font-family: Menlo, serif; + cursor: text; +} + +.inner-content #user-id:before { + content: ""; + position: absolute; + inset: 0; + border-radius: 9px; + padding: 2px; + background: linear-gradient(90.31deg, #ff9933 0.11%, #ff3f33 99.82%); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + mask-composite: exclude; + -webkit-mask-composite: xor; +} + +.main-container > .top-band, +.main-container > .bottom-band { + border-radius: inherit; +} + +.main-container .top-band { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.main-container .bottom-band { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.main-container .sessionButton { + box-sizing: border-box; + background: #ff9933; + border: 1px solid #ff8a15; + box-shadow: 0px 3px 6px rgba(255, 153, 51, 0.16); + border-radius: 6px; + font-size: 16px; +} + +.bottom-cta-container { + display: flex; + justify-content: flex-end; + padding-inline: 21px; + background-color: #212d4f; +} + +.bottom-cta-container .view-code { + padding-block: 11px; + color: #bac9f5; + cursor: pointer; + font-size: 14px; +} + +.bottom-links-container { + display: grid; + grid-template-columns: repeat(4, auto); + margin-bottom: 22px; +} + +.bottom-links-container .link { + display: flex; + align-items: center; + margin-inline-end: 68px; + cursor: pointer; +} + +.bottom-links-container .link:last-child { + margin-right: 0; +} + +.truncate { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.separator-line { + max-width: 100%; +} + +.link .link-icon { + width: 15px; + margin-right: 5px; +} + +@media screen and (max-width: 768px) { + .bottom-links-container { + grid-template-columns: repeat(2, 1fr); + column-gap: 64px; + row-gap: 34px; + } + + .bottom-links-container .link { + margin-inline-end: 0; + } + + .separator-line { + max-width: 200px; + } +} + +@media screen and (max-width: 480px) { + #home-container { + justify-content: start; + padding-block-start: 25px; + } + + .app-container .main-container { + margin-block-end: 90px; + } } diff --git a/boilerplate/fullstack/next-app-dir/app/layout.tsx b/boilerplate/fullstack/next-app-dir/app/layout.tsx index fd4996f9..6ed7d385 100644 --- a/boilerplate/fullstack/next-app-dir/app/layout.tsx +++ b/boilerplate/fullstack/next-app-dir/app/layout.tsx @@ -6,15 +6,34 @@ import { SuperTokensProvider } from "./components/supertokensProvider"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "SuperTokens 💫", + title: "SuperTokens + Nextjs", description: "SuperTokens demo app", }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - - {children} + + +
+
+ + SuperTokens + +
+ +
+
+
{children}
+
+
); diff --git a/boilerplate/fullstack/next-app-dir/app/page.module.css b/boilerplate/fullstack/next-app-dir/app/page.module.css deleted file mode 100644 index e63fa0f1..00000000 --- a/boilerplate/fullstack/next-app-dir/app/page.module.css +++ /dev/null @@ -1,214 +0,0 @@ -.main { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 100vh; -} - -@font-face { - font-family: Menlo; - src: url("../assets/fonts/MenloRegular.ttf"); -} - -.appContainer { - font-family: Rubik, sans-serif; -} - -.appContainer * { - box-sizing: border-box; -} - -.bold400 { - font-variation-settings: "wght" 400; -} - -.bold500 { - font-variation-settings: "wght" 500; -} - -.bold600 { - font-variation-settings: "wght" 600; -} - -.homeContainer { - min-height: 100vh; - min-width: 100vw; - background: url("../assets/images/background.png"); - background-size: cover; - - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.bold700 { - font-variation-settings: "wght" 700; -} - -.mainContainer { - box-shadow: 0px 0px 60px 0px rgba(0, 0, 0, 0.16); - width: min(635px, calc(100% - 24px)); - border-radius: 16px; - margin-block-end: 159px; - background-color: #ffffff; -} - -.successTitle { - line-height: 1; - padding-block: 26px; - background-color: #e7ffed; - text-align: center; - color: #3eb655; - display: flex; - justify-content: center; - align-items: flex-end; - font-size: 20px; -} - -.successIcon { - margin-right: 8px; -} - -.innerContent { - padding-block: 48px; - text-align: center; - display: flex; - flex-direction: column; - align-items: center; -} - -.userId { - position: relative; - padding: 14px 17px; - border-image-slice: 1; - width: min(430px, calc(100% - 30px)); - margin-inline: auto; - margin-block: 11px 23px; - border-radius: 9px; - line-height: 1; - font-family: Menlo, serif; - cursor: text; -} - -.userId:before { - content: ""; - position: absolute; - inset: 0; - border-radius: 9px; - padding: 2px; - background: linear-gradient(90.31deg, #ff9933 0.11%, #ff3f33 99.82%); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; -} - -.topBand, -.bottomBand { - border-radius: inherit; -} - -.topBand { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.bottomBand { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.sessionButton { - padding-left: 13px; - padding-right: 13px; - padding-top: 8px; - padding-bottom: 8px; - cursor: pointer; - color: white; - font-weight: bold; - font-size: 17px; - - box-sizing: border-box; - background: #ff9933; - border: 1px solid #ff8a15; - box-shadow: 0px 3px 6px rgba(255, 153, 51, 0.16); - border-radius: 6px; - font-size: 16px; -} - -.bottomCTAContainer { - display: flex; - justify-content: flex-end; - padding-inline: 21px; - background-color: #212d4f; -} - -.viewCode { - padding-block: 11px; - color: #bac9f5; - cursor: pointer; - font-size: 14px; -} - -.bottomLinksContainer { - display: grid; - grid-template-columns: repeat(4, auto); - margin-bottom: 22px; -} - -.linksContainerLink { - display: flex; - align-items: center; - margin-inline-end: 68px; - cursor: pointer; - text-decoration: none; - color: #000000; -} - -.linksContainerLink:last-child { - margin-right: 0; -} - -.truncate { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -} - -.separatorLine { - max-width: 100%; -} - -.linkIcon { - width: 15px; - margin-right: 5px; -} - -@media screen and (max-width: 768px) { - .bottomLinksContainer { - grid-template-columns: repeat(2, 1fr); - column-gap: 64px; - row-gap: 34px; - } - - .linksContainerLink { - margin-inline-end: 0; - } - - .separatorLine { - max-width: 200px; - } -} - -@media screen and (max-width: 480px) { - .homeContainer { - justify-content: start; - padding-block-start: 25px; - } - - .mainContainer { - margin-block-end: 90px; - } -} diff --git a/boilerplate/fullstack/next-app-dir/app/page.tsx b/boilerplate/fullstack/next-app-dir/app/page.tsx index 8d9b991c..73f5cdc3 100644 --- a/boilerplate/fullstack/next-app-dir/app/page.tsx +++ b/boilerplate/fullstack/next-app-dir/app/page.tsx @@ -1,10 +1,5 @@ import { HomePage } from "./components/home"; -import styles from "./page.module.css"; export default function Home() { - return ( -
- -
- ); + return ; } diff --git a/boilerplate/fullstack/next-app-dir/public/ST.svg b/boilerplate/fullstack/next-app-dir/public/ST.svg new file mode 100644 index 00000000..30b435bd --- /dev/null +++ b/boilerplate/fullstack/next-app-dir/public/ST.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + From e58c7a8338e5308a46582d6b4b2160f5c544c88e Mon Sep 17 00:00:00 2001 From: Joshua Omobola Date: Mon, 30 Dec 2024 16:55:03 +0100 Subject: [PATCH 2/3] chore: update readme --- boilerplate/fullstack/next-app-dir/README.md | 48 +++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/boilerplate/fullstack/next-app-dir/README.md b/boilerplate/fullstack/next-app-dir/README.md index f4490523..573cc047 100644 --- a/boilerplate/fullstack/next-app-dir/README.md +++ b/boilerplate/fullstack/next-app-dir/README.md @@ -6,6 +6,52 @@ A demo implementation of [SuperTokens](https://supertokens.com/) with [Next.js]( This project aims to demonstrate how to integrate SuperTokens into a Nexjs application. Its primary purpose is to serve as an educational tool, but it can also be used as a starting point for your own project. +## Repo Structure + +### Source + +``` +📦 +┣ 📂app +┃ ┣ 📂api +┃ ┃ ┣ 📂auth +┃ ┃ ┃ ┗ 📂[...path] +┃ ┃ ┃ ┗ 📜route.ts +┃ ┃ ┗ 📂user +┃ ┃ ┗ 📜route.ts +┃ ┣ 📂auth +┃ ┃ ┗ 📂[[...path]] +┃ ┃ ┗ 📜page.tsx +┃ ┣ 📂components +┃ ┃ ┣ 📜dashboard.tsx +┃ ┃ ┣ 📜footer.tsx +┃ ┃ ┣ 📜home.tsx +┃ ┃ ┣ 📜sessionAuthForNextJS.tsx +┃ ┃ ┣ 📜sessionInfo.tsx +┃ ┃ ┣ 📜supertokensProvider.tsx +┃ ┃ ┗ 📜tryRefreshClientComponent.tsx +┃ ┣ 📂config +┃ ┃ ┣ 📜appInfo.ts +┃ ┃ ┣ 📜backend.ts +┃ ┃ ┗ 📜frontend.tsx +┃ ┣ 📂dashboard +┃ ┃ ┗ 📜page.tsx +┃ ┣ 📜favicon.ico +┃ ┣ 📜globals.css +┃ ┣ 📜layout.tsx +┃ ┣ 📜page.tsx +┃ ┗ 📜util.ts +┣ 📂assets +┣ 📜middleware.ts +┣ 📜next-env.d.ts +┣ 📜next.config.js +┣ 📜package-lock.json +┣ 📜package.json +┣ 📜README.md +┣ 📂public +┗ 📜tsconfig.json +``` + ### Config #### SuperTokens @@ -16,8 +62,6 @@ If you choose to use this as a starting point for your own project, you can furt ## Application Flow -The application consists of four main parts: - 1. **Root Component (`layout.tsx`)** - Initializes SuperTokens with the provided configuration (generated by the CLI) From 44f2b9be1752dddec779e30810e4dd55320f6d47 Mon Sep 17 00:00:00 2001 From: Joshua Omobola Date: Fri, 17 Jan 2025 18:06:29 +0100 Subject: [PATCH 3/3] fix: PR review feedback --- .../app/auth/[[...path]]/page.tsx | 6 +- .../next-app-dir/app/components/dashboard.tsx | 25 +- .../app/components/dashboardButtons.tsx | 29 ++ .../next-app-dir/app/components/footer.tsx | 58 ---- .../next-app-dir/app/components/home.tsx | 55 ++-- .../app/components/sessionInfo.tsx | 14 - .../app/components/supertokensProvider.tsx | 1 + .../next-app-dir/app/config/frontend.tsx | 5 - .../app/config/frontend/all_auth.tsx | 6 + .../app/config/frontend/dashboardDemo.tsx | 6 + .../app/config/frontend/emailpassword.tsx | 6 + .../app/config/frontend/multifactorauth.tsx | 6 + .../app/config/frontend/passwordless.tsx | 6 + .../app/config/frontend/thirdparty.tsx | 6 + .../frontend/thirdpartyemailpassword.tsx | 6 + .../frontend/thirdpartypasswordless.tsx | 6 + .../fullstack/next-app-dir/app/globals.css | 285 ++++++------------ .../fullstack/next-app-dir/app/layout.tsx | 57 +++- .../fullstack/next-app-dir/public/next.svg | 2 +- 19 files changed, 259 insertions(+), 326 deletions(-) create mode 100644 boilerplate/fullstack/next-app-dir/app/components/dashboardButtons.tsx delete mode 100644 boilerplate/fullstack/next-app-dir/app/components/footer.tsx delete mode 100644 boilerplate/fullstack/next-app-dir/app/components/sessionInfo.tsx diff --git a/boilerplate/fullstack/next-app-dir/app/auth/[[...path]]/page.tsx b/boilerplate/fullstack/next-app-dir/app/auth/[[...path]]/page.tsx index f2f8d601..21feb332 100644 --- a/boilerplate/fullstack/next-app-dir/app/auth/[[...path]]/page.tsx +++ b/boilerplate/fullstack/next-app-dir/app/auth/[[...path]]/page.tsx @@ -17,11 +17,7 @@ export default function Auth() { }, []); if (loaded) { - return ( -
- {SuperTokens.getRoutingComponent(PreBuiltUIList)} -
- ); + return <>{SuperTokens.getRoutingComponent(PreBuiltUIList)}; } return null; diff --git a/boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx b/boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx index b0db9ea5..d4999b0e 100644 --- a/boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx +++ b/boilerplate/fullstack/next-app-dir/app/components/dashboard.tsx @@ -5,9 +5,7 @@ import { CelebrateIcon } from "../../assets/images"; import Image from "next/image"; import { SessionAuthForNextJS } from "./sessionAuthForNextJS"; import { getSessionForSSR } from "../util"; - -import SessionInfo from "./sessionInfo"; -import Footer from "./footer"; +import DashboardButtons from "./dashboardButtons"; export async function DashboardPage() { const cookiesFromReq = await cookies(); @@ -48,20 +46,17 @@ export async function DashboardPage() { */ return ( -
-
-
- Login successful Login successful -
-
-
Your userID is:
-
- {accessTokenPayload.sub} -
- +
+
+ Login successful Login successful +
+
+
Your userID is:
+
+ {accessTokenPayload.sub}
+
-
); diff --git a/boilerplate/fullstack/next-app-dir/app/components/dashboardButtons.tsx b/boilerplate/fullstack/next-app-dir/app/components/dashboardButtons.tsx new file mode 100644 index 00000000..b2799b86 --- /dev/null +++ b/boilerplate/fullstack/next-app-dir/app/components/dashboardButtons.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { signOut } from "supertokens-auth-react/recipe/session"; +import { useRouter } from "next/navigation"; + +export default function DashboardButtons() { + const router = useRouter(); + + const callAPIClicked = async () => { + const userInfoResponse = await fetch("http://localhost:3000/api/user"); + alert(JSON.stringify(await userInfoResponse.json())); + }; + + async function logoutClicked() { + await signOut(); + router.push("/"); + } + + return ( +
+ + +
+ ); +} diff --git a/boilerplate/fullstack/next-app-dir/app/components/footer.tsx b/boilerplate/fullstack/next-app-dir/app/components/footer.tsx deleted file mode 100644 index 612288ae..00000000 --- a/boilerplate/fullstack/next-app-dir/app/components/footer.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client"; - -import { BlogsIcon, GuideIcon, SeparatorLine, SignOutIcon } from "../../assets/images"; -import Image from "next/image"; -import { recipeDetails } from "../config/frontend"; -import Session from "supertokens-auth-react/recipe/session/index.js"; -import SuperTokens from "supertokens-auth-react"; - -export interface Link { - name: string; - onClick: () => void; - icon: string; -} - -function openLink(url: string) { - window.open(url, "_blank"); -} - -async function logoutClicked() { - await Session.signOut(); - SuperTokens.redirectToAuth(); -} - -const links: Link[] = [ - { - name: "Blogs", - onClick: () => openLink("https://supertokens.com/blog"), - icon: BlogsIcon, - }, - { - name: "Documentation", - onClick: () => openLink(recipeDetails.docsLink), - icon: GuideIcon, - }, - { - name: "Sign Out", - onClick: logoutClicked, - icon: SignOutIcon, - }, -]; - -export default function Footer() { - return ( - <> -
- {links.map((link) => ( -
- {link.name} -
- {link.name} -
-
- ))} -
- separator - - ); -} diff --git a/boilerplate/fullstack/next-app-dir/app/components/home.tsx b/boilerplate/fullstack/next-app-dir/app/components/home.tsx index af2f2a52..a62bc195 100644 --- a/boilerplate/fullstack/next-app-dir/app/components/home.tsx +++ b/boilerplate/fullstack/next-app-dir/app/components/home.tsx @@ -1,28 +1,49 @@ +"use client"; + import Link from "next/link"; +import { useSessionContext } from "supertokens-auth-react/recipe/session"; export function HomePage() { + const session = useSessionContext(); + + if (session.loading) { + return null; + } + return ( -
-
+ <> +
SuperTokens x - Next.js -
-
+ Next + +
-

- SuperTokens x Nextjs
example project -

-
- - Sign-up / Login - - - Dashboard - +

+ SuperTokens x Next.js
example project +

+
+ {session.doesSessionExist ? ( +

+ You're signed in already,
check out the Dashboard! 👇 +

+ ) : ( +

Sign-in to continue

+ )}
+
-
-
+ + ); } diff --git a/boilerplate/fullstack/next-app-dir/app/components/sessionInfo.tsx b/boilerplate/fullstack/next-app-dir/app/components/sessionInfo.tsx deleted file mode 100644 index 4f4f19d3..00000000 --- a/boilerplate/fullstack/next-app-dir/app/components/sessionInfo.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -export default function SessionInfo() { - const fetchUserData = async () => { - const userInfoResponse = await fetch("http://localhost:3000/api/user"); - alert(JSON.stringify(await userInfoResponse.json())); - }; - - return ( -
fetchUserData()} className="sessionButton"> - Call API -
- ); -} diff --git a/boilerplate/fullstack/next-app-dir/app/components/supertokensProvider.tsx b/boilerplate/fullstack/next-app-dir/app/components/supertokensProvider.tsx index 8838a7e1..8a06dce5 100644 --- a/boilerplate/fullstack/next-app-dir/app/components/supertokensProvider.tsx +++ b/boilerplate/fullstack/next-app-dir/app/components/supertokensProvider.tsx @@ -1,4 +1,5 @@ "use client"; + import React from "react"; import { SuperTokensWrapper } from "supertokens-auth-react"; import SuperTokensReact from "supertokens-auth-react"; diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend.tsx index b9daeac3..b799df72 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend.tsx @@ -24,11 +24,6 @@ export const frontendConfig = (): SuperTokensConfig => { }, }; }, - getRedirectionURL: async (context) => { - if (context.action === "SUCCESS" && context.newSessionCreated) { - return "/dashboard"; - } - }, }; }; diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend/all_auth.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend/all_auth.tsx index 8bf15c79..4990cc70 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend/all_auth.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend/all_auth.tsx @@ -35,6 +35,12 @@ export const frontendConfig = (): SuperTokensConfig => { }), Session.init(), ], + getRedirectionURL: async (context: any) => { + if (context.action === "SUCCESS") { + return "/dashboard"; + } + return undefined; + }, windowHandler: (orig) => { return { ...orig, diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend/dashboardDemo.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend/dashboardDemo.tsx index f3b96fc3..2e865ab0 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend/dashboardDemo.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend/dashboardDemo.tsx @@ -16,6 +16,12 @@ export const frontendConfig = (): SuperTokensConfig => { return { appInfo, recipeList: [EmailPasswordReact.init(), Session.init()], + getRedirectionURL: async (context: any) => { + if (context.action === "SUCCESS") { + return "/dashboard"; + } + return undefined; + }, windowHandler: (orig) => { return { ...orig, diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend/emailpassword.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend/emailpassword.tsx index f3b96fc3..2e865ab0 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend/emailpassword.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend/emailpassword.tsx @@ -16,6 +16,12 @@ export const frontendConfig = (): SuperTokensConfig => { return { appInfo, recipeList: [EmailPasswordReact.init(), Session.init()], + getRedirectionURL: async (context: any) => { + if (context.action === "SUCCESS") { + return "/dashboard"; + } + return undefined; + }, windowHandler: (orig) => { return { ...orig, diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend/multifactorauth.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend/multifactorauth.tsx index bf1c54a9..00b459fd 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend/multifactorauth.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend/multifactorauth.tsx @@ -44,6 +44,12 @@ export const frontendConfig = (): SuperTokensConfig => { TOTPReact.init(), Session.init(), ], + getRedirectionURL: async (context: any) => { + if (context.action === "SUCCESS") { + return "/dashboard"; + } + return undefined; + }, windowHandler: (orig) => { return { ...orig, diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend/passwordless.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend/passwordless.tsx index d32de20d..762cf399 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend/passwordless.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend/passwordless.tsx @@ -21,6 +21,12 @@ export const frontendConfig = (): SuperTokensConfig => { }), Session.init(), ], + getRedirectionURL: async (context: any) => { + if (context.action === "SUCCESS") { + return "/dashboard"; + } + return undefined; + }, windowHandler: (orig) => { return { ...orig, diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdparty.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdparty.tsx index 98e00015..2695cf6d 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdparty.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdparty.tsx @@ -27,6 +27,12 @@ export const frontendConfig = (): SuperTokensConfig => { }), Session.init(), ], + getRedirectionURL: async (context: any) => { + if (context.action === "SUCCESS") { + return "/dashboard"; + } + return undefined; + }, windowHandler: (orig) => { return { ...orig, diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdpartyemailpassword.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdpartyemailpassword.tsx index 8af3e44c..f54e7812 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdpartyemailpassword.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdpartyemailpassword.tsx @@ -30,6 +30,12 @@ export const frontendConfig = (): SuperTokensConfig => { }), Session.init(), ], + getRedirectionURL: async (context: any) => { + if (context.action === "SUCCESS") { + return "/dashboard"; + } + return undefined; + }, windowHandler: (orig) => { return { ...orig, diff --git a/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdpartypasswordless.tsx b/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdpartypasswordless.tsx index eba0abdc..cccb081d 100644 --- a/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdpartypasswordless.tsx +++ b/boilerplate/fullstack/next-app-dir/app/config/frontend/thirdpartypasswordless.tsx @@ -32,6 +32,12 @@ export const frontendConfig = (): SuperTokensConfig => { }), Session.init(), ], + getRedirectionURL: async (context: any) => { + if (context.action === "SUCCESS") { + return "/dashboard"; + } + return undefined; + }, windowHandler: (orig) => { return { ...orig, diff --git a/boilerplate/fullstack/next-app-dir/app/globals.css b/boilerplate/fullstack/next-app-dir/app/globals.css index 03749fa6..d03d0e80 100644 --- a/boilerplate/fullstack/next-app-dir/app/globals.css +++ b/boilerplate/fullstack/next-app-dir/app/globals.css @@ -1,3 +1,12 @@ +/* Base styles */ +:root { + --primary-color: #ff9933; + --primary-hover: #ff8a15; + --success-color: #3eb655; + --success-bg: #e7ffed; + --border-color: #e0e0e0; +} + @font-face { font-family: Menlo; src: url("../assets/fonts/MenloRegular.ttf"); @@ -11,32 +20,17 @@ body { -moz-osx-font-smoothing: grayscale; } -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; -} - -.logos { - display: flex; - align-items: center; - gap: 20px; - padding-bottom: 80px; -} - -.logos span { - font-size: 8rem; - font-weight: bold; -} - -.logos img { - width: 240px; +* { + box-sizing: border-box; } -.App { +/* Layout */ +.app-container { display: flex; flex-direction: column; width: 100%; - height: 100%; - font-family: Rubik; + min-height: 100vh; + font-family: Rubik, sans-serif; } .fill { @@ -46,239 +40,140 @@ code { flex: 1; } -.buttons { - display: flex; - gap: 4px; -} - -.sessionButton { - padding-left: 13px; - padding-right: 13px; - padding-top: 8px; - padding-bottom: 8px; - background-color: black; - border-radius: 10px; - cursor: pointer; - color: white; - font-weight: bold; - font-size: 17px; - text-decoration: none; -} - +/* Header */ header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 10px; + border-bottom: 2px solid var(--border-color); padding: 8px; - border-bottom: 2px solid #e0e0e0; } -.header-container { - height: 100%; +header nav { display: flex; align-items: center; + justify-content: space-between; + width: 100%; } -.header-container-right { - display: flex; - gap: 12px; -} - -.header-container-right a { - color: #ff8a15; - font-weight: 600; - - &:hover { - color: #ff9933; - } -} - -.header-container img { +header img { width: 40px; } -.app-container { - font-family: Rubik, sans-serif; +header ul { + display: flex; + gap: 12px; + list-style: none; + margin: 0; + padding: 0; } -.app-container * { - box-sizing: border-box; +header a { + color: var(--primary-color); + font-weight: 600; + text-decoration: none; } -.bold-400 { - font-variation-settings: "wght" 400; +header a:hover { + color: var(--primary-hover); } -.bold-500 { - font-variation-settings: "wght" 500; +/* Home page */ +#home-container { + align-items: center; + min-height: calc(100vh - 58px); + background: url("../assets/images/background.png") center/cover; } -.bold-600 { - font-variation-settings: "wght" 600; +.logos { + display: flex; + align-items: center; + gap: 20px; + padding-bottom: 80px; } -#home-container { - align-items: center; - min-height: calc(100vh - 58px); - background: url("../assets/images/background.png"); - background-size: cover; +.logos span { + font-size: 8rem; + font-weight: bold; } -.bold-700 { - font-variation-settings: "wght" 700; +.logos img { + height: 240px; + width: auto; } -.app-container .main-container { - box-shadow: 0px 0px 60px 0px rgba(0, 0, 0, 0.16); +/* Main content */ +.main-container { + box-shadow: 0 0 60px rgba(0, 0, 0, 0.16); width: min(635px, calc(100% - 24px)); border-radius: 16px; - margin-block-end: 159px; - background-color: #ffffff; + margin-bottom: 159px; + background-color: white; } -.main-container .success-title { - line-height: 1; - padding-block: 26px; - background-color: #e7ffed; - text-align: center; - color: #3eb655; +.success-title { display: flex; justify-content: center; - align-items: flex-end; + align-items: center; + padding: 26px; + background-color: var(--success-bg); + color: var(--success-color); font-size: 20px; + line-height: 1; } -.success-title img.success-icon { +.success-title img { margin-right: 8px; } -.main-container .inner-content { - padding-block: 48px; +.inner-content { + padding: 48px; text-align: center; display: flex; flex-direction: column; align-items: center; } -.inner-content #user-id { +/* User ID display */ +#user-id { position: relative; - padding: 14px 17px; - border-image-slice: 1; width: min(430px, calc(100% - 30px)); - margin-inline: auto; - margin-block: 11px 23px; + margin: 11px auto 23px; + padding: 14px 17px; border-radius: 9px; + font-family: Menlo, monospace; line-height: 1; - font-family: Menlo, serif; cursor: text; + border: 3px solid var(--primary-color); } -.inner-content #user-id:before { - content: ""; - position: absolute; - inset: 0; - border-radius: 9px; - padding: 2px; - background: linear-gradient(90.31deg, #ff9933 0.11%, #ff3f33 99.82%); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask-composite: exclude; - -webkit-mask-composite: xor; -} - -.main-container > .top-band, -.main-container > .bottom-band { - border-radius: inherit; -} - -.main-container .top-band { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.main-container .bottom-band { - border-top-left-radius: 0; - border-top-right-radius: 0; +/* Buttons and navigation */ +.buttons { + display: flex; + gap: 4px; } -.main-container .sessionButton { - box-sizing: border-box; - background: #ff9933; - border: 1px solid #ff8a15; - box-shadow: 0px 3px 6px rgba(255, 153, 51, 0.16); +.dashboard-button { + padding: 8px 13px; + background: var(--primary-color); + border: 1px solid var(--primary-hover); border-radius: 6px; + box-shadow: 0 3px 6px rgba(255, 153, 51, 0.16); + color: white; + font-weight: bold; font-size: 16px; -} - -.bottom-cta-container { - display: flex; - justify-content: flex-end; - padding-inline: 21px; - background-color: #212d4f; -} - -.bottom-cta-container .view-code { - padding-block: 11px; - color: #bac9f5; cursor: pointer; - font-size: 14px; -} - -.bottom-links-container { - display: grid; - grid-template-columns: repeat(4, auto); - margin-bottom: 22px; -} - -.bottom-links-container .link { - display: flex; - align-items: center; - margin-inline-end: 68px; - cursor: pointer; -} - -.bottom-links-container .link:last-child { - margin-right: 0; -} - -.truncate { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -} - -.separator-line { - max-width: 100%; + text-decoration: none; } -.link .link-icon { - width: 15px; - margin-right: 5px; +/* Footer */ +footer { + padding: 10px; } -@media screen and (max-width: 768px) { - .bottom-links-container { - grid-template-columns: repeat(2, 1fr); - column-gap: 64px; - row-gap: 34px; - } - - .bottom-links-container .link { - margin-inline-end: 0; - } - - .separator-line { - max-width: 200px; - } +footer a { + color: var(--primary-color); + font-weight: 600; + /* text-decoration: none; */ } -@media screen and (max-width: 480px) { - #home-container { - justify-content: start; - padding-block-start: 25px; - } - - .app-container .main-container { - margin-block-end: 90px; - } +footer a:hover { + color: var(--primary-hover); } diff --git a/boilerplate/fullstack/next-app-dir/app/layout.tsx b/boilerplate/fullstack/next-app-dir/app/layout.tsx index 6ed7d385..6a71f1f9 100644 --- a/boilerplate/fullstack/next-app-dir/app/layout.tsx +++ b/boilerplate/fullstack/next-app-dir/app/layout.tsx @@ -2,6 +2,9 @@ import "./globals.css"; import type { Metadata } from "next"; import { Inter } from "next/font/google"; import { SuperTokensProvider } from "./components/supertokensProvider"; +import { SeparatorLine } from "../assets/images"; +import Link from "next/link"; +import Image from "next/image"; const inter = Inter({ subsets: ["latin"] }); @@ -15,23 +18,45 @@ export default function RootLayout({ children }: { children: React.ReactNode }) -
-
- - SuperTokens - -
- -
-
{children}
+
+ +
+
+ {children} + + separator +
diff --git a/boilerplate/fullstack/next-app-dir/public/next.svg b/boilerplate/fullstack/next-app-dir/public/next.svg index 5174b28c..06a2bd6e 100644 --- a/boilerplate/fullstack/next-app-dir/public/next.svg +++ b/boilerplate/fullstack/next-app-dir/public/next.svg @@ -1 +1 @@ - \ No newline at end of file +Next.js \ No newline at end of file