+ )
+}
diff --git a/src/pages/auth/forgot-password.tsx b/src/app/(auth)/forgot-password/page.tsx
similarity index 71%
rename from src/pages/auth/forgot-password.tsx
rename to src/app/(auth)/forgot-password/page.tsx
index 9c36f6f3..ee89a1ac 100644
--- a/src/pages/auth/forgot-password.tsx
+++ b/src/app/(auth)/forgot-password/page.tsx
@@ -1,10 +1,11 @@
+"use client"
import Layout from "src/core/layouts/Layout"
import { LabeledTextField } from "src/core/components/LabeledTextField"
import { Form, FORM_ERROR } from "src/core/components/Form"
-import { ForgotPassword } from "src/auth/schemas"
-import forgotPassword from "src/auth/mutations/forgotPassword"
+import { ForgotPassword } from "src/app/(auth)/schemas"
+import forgotPassword from "src/app/(auth)/mutations/forgotPassword"
import { useMutation } from "@blitzjs/rpc"
-import { BlitzPage, Routes } from "@blitzjs/next"
+import { BlitzPage } from "@blitzjs/next"
import Link from "next/link"
const ForgotPasswordPage: BlitzPage = () => {
@@ -14,20 +15,25 @@ const ForgotPasswordPage: BlitzPage = () => {
-
Forgot your password?
+
+ Forgot your password?
+
{isSuccess ? (
Request Submitted
- If your email is in our system, you will receive instructions to reset your
- password shortly.
+ If your email is in our system, you will receive instructions
+ to reset your password shortly.
) : (
@@ -40,12 +46,17 @@ const ForgotPasswordPage: BlitzPage = () => {
await forgotPasswordMutation(values)
} catch (error: any) {
return {
- [FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
+ [FORM_ERROR]:
+ "Sorry, we had an unexpected error. Please try again.",
}
}
}}
>
-
+
)}
diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx
new file mode 100644
index 00000000..6c573fde
--- /dev/null
+++ b/src/app/(auth)/layout.tsx
@@ -0,0 +1,12 @@
+import { useAuthenticatedBlitzContext } from "../blitz-server"
+
+export default async function AuthLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ await useAuthenticatedBlitzContext({
+ redirectAuthenticatedTo: "/",
+ })
+ return <>{children}>
+}
diff --git a/src/pages/auth/login.tsx b/src/app/(auth)/login/page.tsx
similarity index 62%
rename from src/pages/auth/login.tsx
rename to src/app/(auth)/login/page.tsx
index 78b3dd4d..39c69a63 100644
--- a/src/pages/auth/login.tsx
+++ b/src/app/(auth)/login/page.tsx
@@ -1,8 +1,9 @@
-import { useRouter } from "next/router"
-import Layout from "src/core/layouts/Layout"
-import { LoginForm } from "src/auth/components/LoginForm"
-import { BlitzPage, Routes } from "@blitzjs/next"
+"use client"
+import { BlitzPage } from "@blitzjs/next"
+import { useRouter } from "next/navigation"
import Link from "next/link"
+import LoginForm from "src/app/(auth)/components/LoginForm"
+import Layout from "src/core/layouts/Layout"
const LoginPage: BlitzPage = () => {
const router = useRouter()
@@ -11,12 +12,15 @@ const LoginPage: BlitzPage = () => {
Log In
@@ -35,14 +44,17 @@ const UserExceptionWrapper = ({ error }: { error: Error & Record }) =>
Not a member yet?{" "}
Create an account for free!
) : (
-
+
Back to Home
)}
@@ -52,8 +64,12 @@ const UserExceptionWrapper = ({ error }: { error: Error & Record }) =>
)
+// TODO: merge w ladderly-3/src/app/error.tsx
function RootErrorFallback({ error }: ErrorFallbackProps) {
- if (error instanceof AuthenticationError || error instanceof AuthorizationError) {
+ if (
+ error instanceof AuthenticationError ||
+ error instanceof AuthorizationError
+ ) {
return
}
diff --git a/src/pages/about.tsx b/src/pages/about.tsx
index b64e2f79..af1bcb1e 100644
--- a/src/pages/about.tsx
+++ b/src/pages/about.tsx
@@ -3,68 +3,86 @@ import Link from "next/link"
import { LargeCard } from "src/core/components/LargeCard"
import { LadderlyPageWrapper } from "src/core/components/page-wrapper/LadderlyPageWrapper"
-import styles from "src/styles/Home.module.css"
+import styles from "src/app/styles/Home.module.css"
const AboutPage: BlitzPage = () => {
return (
-
Discover the Ladderly Difference
+
+ Discover the Ladderly Difference
+
- Ladderly is an ecosystem of passionate learners and innovators. Ladderly provides a set of
- tools and educational programs dedicated to accelerating your journey in tech.
+ Ladderly is an ecosystem of passionate learners and innovators.
+ Ladderly provides a set of tools and educational programs dedicated to
+ accelerating your journey in tech.
+
+
+ Learn to code, climb your career ladder, and join the revolution.
-
Learn to code, climb your career ladder, and join the revolution.
-
Our Core Values
+
+ Our Core Values
+
- Generosity & Community: Ladderly thrives on mutual support. Members
- help each other learn, grow, and succeed. Our strength lies in our community.
+ Generosity & Community: Ladderly thrives on
+ mutual support. Members help each other learn, grow, and succeed.
+ Our strength lies in our community.
- Freedom: We value freedom. We champion free services to uplift all,
- especially those less privileged. We value open source and open science.
+ Freedom: We value freedom. We champion free
+ services to uplift all, especially those less privileged. We value
+ open source and open science.
- Open Source: We advocate for open-source technology. The curriculum
- we teach, the research we produce, and this website are all open source!
+ Open Source: We advocate for open-source
+ technology. The curriculum we teach, the research we produce, and
+ this website are all open source!
- Science: At Ladderly, we emphasize data-driven and evidence-based
- strategies for our students. We continuously experiment, learn, share back with the
- community, and improve our tools based on what we find.
+ Science: At Ladderly, we emphasize data-driven
+ and evidence-based strategies for our students. We continuously
+ experiment, learn, share back with the community, and improve our
+ tools based on what we find.
- Diversity: Our community thrives on diverse minds coming together.
- Multiple perspectives, united by a shared vision, lead us to truth and innovation.
+ Diversity: Our community thrives on diverse minds
+ coming together. Multiple perspectives, united by a shared vision,
+ lead us to truth and innovation.
- Personalized & Intelligent Defaults: We are literally immune to
- analysis paralysis! Instead of overwhelming choices, we present intelligent defaults,
- with prioritized alternatives and fallback strategies if the primary approach doesn
+ Personalized & Intelligent Defaults: We are
+ literally immune to analysis paralysis! Instead of overwhelming
+ choices, we present intelligent defaults, with prioritized
+ alternatives and fallback strategies if the primary approach doesn
{`'`}t work for you.
- Curation & Humility: We celebrate the expertise of others. If someone
- else does it better, we happily refer you to external resources, instead of trying to
- be the best at everything. Partnership, honesty, and continuous improvement are our
- cornerstones. Curation is a major part of our value proposition.
+ Curation & Humility: We celebrate the expertise
+ of others. If someone else does it better, we happily refer you to
+ external resources, instead of trying to be the best at
+ everything. Partnership, honesty, and continuous improvement are
+ our cornerstones. Curation is a major part of our value
+ proposition.
- Quality of Life: We endorse a high-performance lifestyle, underpinned
- by health, rest, and joy. Celebrate achievements, big or small, and advance your
- skills and career, without encouraging workaholism or other unhealthy habits.
+ Quality of Life: We endorse a high-performance
+ lifestyle, underpinned by health, rest, and joy. Celebrate
+ achievements, big or small, and advance your skills and career,
+ without encouraging workaholism or other unhealthy habits.
-
Start Now for Free!
+
+ Start Now for Free!
+
Sign up{" "}
-
+
here
{" "}
and check out the{" "}
diff --git a/src/pages/api/rpc/[[...blitz]].ts b/src/pages/api/rpc/[[...blitz]].ts
index c6096b01..5fcde42f 100644
--- a/src/pages/api/rpc/[[...blitz]].ts
+++ b/src/pages/api/rpc/[[...blitz]].ts
@@ -1,4 +1,6 @@
+// Note: This stays in the /pages folder for the time being
+
import { rpcHandler } from "@blitzjs/rpc"
-import { api } from "src/blitz-server"
+import { api } from "src/app/blitz-server"
-export default api(rpcHandler({ onError: console.log }))
+export default api(rpcHandler({ onError: (error, ctx) => console.log(error) }))
diff --git a/src/pages/blog/2024-04-20-hook-fundamentals.md b/src/pages/blog/2024-04-20-hook-fundamentals.md
index c27032da..80e45e0b 100644
--- a/src/pages/blog/2024-04-20-hook-fundamentals.md
+++ b/src/pages/blog/2024-04-20-hook-fundamentals.md
@@ -34,7 +34,7 @@ Please do refer to the official documentation on the hooks. Here are some additi
1. A side effect is a logical operation that is not directly returned to the client or consumer. The client or consumer can operate in total ignorance of the side effect.
2. Baking cookies is an analogy. A person might bake cookies with the direct purpose of eating cookies, but as a side effect the house might end up smelling like cookies. Whether or not the house smells like cookies does not determine the ability to actually eat cookies.
3. Side effects often involve calling systems outside of React itself to send or receive data.
- 1. Those externl systems can be client-side or they might have their own remote server. `useEffect` can wrap async code.
+ 1. Those external systems can be client-side or they might have their own remote server. `useEffect` can wrap async code.
2. In the next section we look at a use case involving [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
3. Another common use case is the use case of an enterprise logger. In this case, our software logs something to the server, but the React render chain doesn't directly know or care about any such logs.
2. `useState` enables state management, data manipulation, and data persistence.
@@ -53,7 +53,7 @@ As part of that project, students build a custom hook called `useLocalStorage` w
In this section, I transcribe a prompt conversation with GPT-4. I recommend you read this entire article, not just this section, twice. This is for two reasons:
1. GPT-4 begins with a complex implementation and I strip the complexity away with chained prompts. This means you will see the simplest implementation last. After you understand that, it will be easier to understand the former implementations.
-2. After you have seen these implementations, reviewing earlier sections which discuss hooks at a higher level will make more sense and the repitition between low and high level descriptions will lead to stronger concept reinforcement.
+2. After you have seen these implementations, reviewing earlier sections which discuss hooks at a higher level will make more sense and the repetition between low and high level descriptions will lead to stronger concept reinforcement.
### The Transcript:
@@ -63,7 +63,7 @@ For a Next.js application using the app router, provide an example implementatio
ChatGPT:
Below is an example implementation of a custom useLocalStorage hook for a Next.js application that adheres to the style of calling built-in hooks explicitly on their object container (React.useState, React.useEffect, etc.). This approach is particularly useful for distinguishing React hooks from custom hooks or other functions.
-This custom hook, useLocalStorage, allows you to store a value in local storage so that it persists across browser sessions. It also synchronizes the state within the component whenever the stored value changes.
+This custom hook, useLocalStorage, allows you to store a value in localStorage so that it persists across browser sessions. It also synchronizes the state within the component whenever the stored value changes.
Implementation of useLocalStorage.ts
@@ -82,7 +82,7 @@ function useLocalStorage(
// Define a state managed by this hook
const [storedValue, setStoredValue] = React.useState(() => {
try {
- // Get from local storage by key
+ // Get from localStorage by key
const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue
return item
@@ -107,7 +107,7 @@ function useLocalStorage(
value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore)
- // Save to local storage
+ // Save to localStorage
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// A more advanced implementation could handle errors, for example by retrying to save the value.
@@ -126,8 +126,8 @@ export default useLocalStorage
Key Features of the useLocalStorage Hook:
1. Lazy Initialization: The hook initializes the state lazily. The state will only get the value from localStorage when it is first used. This is achieved through a function passed to React.useState().
-2. Error Handling: Basic error handling is included, which logs errors to the console if there are issues accessing local storage or parsing stored values.
-3. Synchronization with Local Storage: The state is automatically synchronized with local storage using React.useEffect() and React.useState() to ensure that updates to the state are reflected in local storage.
+2. Error Handling: Basic error handling is included, which logs errors to the console if there are issues accessing localStorage or parsing stored values.
+3. Synchronization with localStorage: The state is automatically synchronized with localStorage using React.useEffect() and React.useState() to ensure that updates to the state are reflected in localStorage.
4. Function Updates: The setter function setValue can accept a new state value directly or a function that updates the state based on the previous value, similar to React.setState().
This hook can be directly used in any React component to manage state that needs to persist across sessions. It's particularly useful for preferences, session states, or any data that should be retained locally.
@@ -152,7 +152,7 @@ function useLocalStorage(key, initialValue) {
// Define a state managed by this hook
const [storedValue, setStoredValue] = React.useState(() => {
try {
- // Get from local storage by key
+ // Get from localStorage by key
const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue
return item
@@ -177,7 +177,7 @@ function useLocalStorage(key, initialValue) {
value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore)
- // Save to local storage
+ // Save to localStorage
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// A more advanced implementation could handle errors, for example by retrying to save the value.
@@ -232,7 +232,7 @@ export default useLocalStorage
Simplifications Made:
-1. No Error Handling: Any issues with local storage access or JSON parsing will lead to runtime errors that are not caught or logged.
+1. No Error Handling: Any issues with localStorage access or JSON parsing will lead to runtime errors that are not caught or logged.
2. No React.useCallback: The setter function is not wrapped in React.useCallback, which means it does not optimize the function's dependency on re-renders. This might result in less efficient rendering behavior in components that use this hook extensively, especially in large applications.
3. No Comments: There are no explanatory comments, which makes the code more compact but less understandable for someone not familiar with what it's supposed to do.
diff --git a/src/pages/blog/[slug].tsx b/src/pages/blog/[slug].tsx
index 3f1a625d..107404a4 100644
--- a/src/pages/blog/[slug].tsx
+++ b/src/pages/blog/[slug].tsx
@@ -12,7 +12,7 @@ import { unified } from "unified"
import { LadderlyPageWrapper } from "src/core/components/page-wrapper/LadderlyPageWrapper"
import "highlight.js/styles/github-dark.css"
-import styles from "src/styles/Home.module.css"
+import styles from "src/app/styles/Home.module.css"
const tipUrl =
"https://checkout.stripe.com/c/pay/cs_live_a10YyjvS4xEbZJ9Th74ZTitGd2NZoHBILwU0K8AdL1P5INh5a9ry7h1Bj9#fidkdWxOYHwnPyd1blppbHNgWlIzUk5qNVddT2AyYGM8PURQN01fTUFSYjU1bX9nPX1KZ1InKSd1aWxrbkB9dWp2YGFMYSc%2FJ2BTZDxAMjdgYmBpQ2NWYmNcXycpJ3dgY2B3d2B3SndsYmxrJz8nbXFxdXY%2FKipycnIraWRhYWB3aXwrbGoqJ3gl"
diff --git a/src/pages/checklist-items/[checklistItemId].tsx b/src/pages/checklist-items/[checklistItemId].tsx
index a13d0f93..49cfc24b 100644
--- a/src/pages/checklist-items/[checklistItemId].tsx
+++ b/src/pages/checklist-items/[checklistItemId].tsx
@@ -1,14 +1,13 @@
-import { Suspense } from "react"
-import { Routes } from "@blitzjs/next"
+import { Routes, useParam } from "@blitzjs/next"
+import { useMutation, useQuery } from "@blitzjs/rpc"
import Head from "next/head"
import Link from "next/link"
import { useRouter } from "next/router"
-import { useQuery, useMutation } from "@blitzjs/rpc"
-import { useParam } from "@blitzjs/next"
+import { Suspense } from "react"
+import deleteChecklistItem from "src/app/checklist-items/mutations/deleteChecklistItem"
+import getChecklistItem from "src/app/checklist-items/queries/getChecklistItem"
import Layout from "src/core/layouts/Layout"
-import getChecklistItem from "src/checklist-items/queries/getChecklistItem"
-import deleteChecklistItem from "src/checklist-items/mutations/deleteChecklistItem"
export const ChecklistItem = () => {
const router = useRouter()
@@ -66,6 +65,8 @@ const ShowChecklistItemPage = () => {
}
ShowChecklistItemPage.authenticate = true
-ShowChecklistItemPage.getLayout = (page) => {page}
+ShowChecklistItemPage.getLayout = (page) => (
+ {page}
+)
export default ShowChecklistItemPage
diff --git a/src/pages/checklist-items/[checklistItemId]/edit.tsx b/src/pages/checklist-items/[checklistItemId]/edit.tsx
index b84c2b8a..b26cedb0 100644
--- a/src/pages/checklist-items/[checklistItemId]/edit.tsx
+++ b/src/pages/checklist-items/[checklistItemId]/edit.tsx
@@ -1,17 +1,19 @@
-import { Suspense } from "react"
-import { Routes } from "@blitzjs/next"
+import { Routes, useParam } from "@blitzjs/next"
+import { useMutation, useQuery } from "@blitzjs/rpc"
import Head from "next/head"
import Link from "next/link"
import { useRouter } from "next/router"
-import { useQuery, useMutation } from "@blitzjs/rpc"
-import { useParam } from "@blitzjs/next"
+import { Suspense } from "react"
-import Layout from "src/core/layouts/Layout"
-import { UpdateChecklistItemSchema } from "src/checklist-items/schemas"
-import getChecklistItem from "src/checklist-items/queries/getChecklistItem"
-import updateChecklistItem from "src/checklist-items/mutations/updateChecklistItem"
-import { ChecklistItemForm, FORM_ERROR } from "src/checklist-items/components/ChecklistItemForm"
import { RoleEnum } from "db"
+import {
+ ChecklistItemForm,
+ FORM_ERROR,
+} from "src/app/checklist-items/components/ChecklistItemForm"
+import updateChecklistItem from "src/app/checklist-items/mutations/updateChecklistItem"
+import getChecklistItem from "src/app/checklist-items/queries/getChecklistItem"
+import { UpdateChecklistItemSchema } from "src/app/checklist-items/schemas"
+import Layout from "src/core/layouts/Layout"
export const EditChecklistItem = () => {
const router = useRouter()
@@ -46,7 +48,9 @@ export const EditChecklistItem = () => {
id: checklistItem.id,
})
await setQueryData(updated)
- await router.push(Routes.ShowChecklistItemPage({ checklistItemId: updated.id }))
+ await router.push(
+ Routes.ShowChecklistItemPage({ checklistItemId: updated.id })
+ )
} catch (error: any) {
console.error(error)
return {
@@ -76,6 +80,8 @@ const EditChecklistItemPage = () => {
}
EditChecklistItemPage.authenticate = { role: RoleEnum.ADMIN }
-EditChecklistItemPage.getLayout = (page) => {page}
+EditChecklistItemPage.getLayout = (page) => (
+ {page}
+)
export default EditChecklistItemPage
diff --git a/src/pages/checklist-items/index.tsx b/src/pages/checklist-items/index.tsx
index 49441c26..1a174f3f 100644
--- a/src/pages/checklist-items/index.tsx
+++ b/src/pages/checklist-items/index.tsx
@@ -5,7 +5,7 @@ import Link from "next/link"
import { usePaginatedQuery } from "@blitzjs/rpc"
import { useRouter } from "next/router"
import Layout from "src/core/layouts/Layout"
-import getChecklistItems from "src/checklist-items/queries/getChecklistItems"
+import getChecklistItems from "src/app/checklist-items/queries/getChecklistItems"
const ITEMS_PER_PAGE = 100
diff --git a/src/pages/checklist-items/new.tsx b/src/pages/checklist-items/new.tsx
index 2be92dff..0d3d025d 100644
--- a/src/pages/checklist-items/new.tsx
+++ b/src/pages/checklist-items/new.tsx
@@ -1,12 +1,15 @@
import { Routes } from "@blitzjs/next"
+import { useMutation } from "@blitzjs/rpc"
import Link from "next/link"
import { useRouter } from "next/router"
-import { useMutation } from "@blitzjs/rpc"
-import Layout from "src/core/layouts/Layout"
-import { CreateChecklistItemSchema } from "src/checklist-items/schemas"
-import createChecklistItem from "src/checklist-items/mutations/createChecklistItem"
-import { ChecklistItemForm, FORM_ERROR } from "src/checklist-items/components/ChecklistItemForm"
import { Suspense } from "react"
+import {
+ ChecklistItemForm,
+ FORM_ERROR,
+} from "src/app/checklist-items/components/ChecklistItemForm"
+import createChecklistItem from "src/app/checklist-items/mutations/createChecklistItem"
+import { CreateChecklistItemSchema } from "src/app/checklist-items/schemas"
+import Layout from "src/core/layouts/Layout"
const NewChecklistItemPage = () => {
const router = useRouter()
diff --git a/src/pages/checklists/[checklistId].tsx b/src/pages/checklists/[checklistId].tsx
index 86fcc53e..2e579c24 100644
--- a/src/pages/checklists/[checklistId].tsx
+++ b/src/pages/checklists/[checklistId].tsx
@@ -7,8 +7,8 @@ import { useQuery, useMutation } from "@blitzjs/rpc"
import { useParam } from "@blitzjs/next"
import Layout from "src/core/layouts/Layout"
-import getChecklist from "src/checklists/queries/getChecklist"
-import deleteChecklist from "src/checklists/mutations/deleteChecklist"
+import getChecklist from "src/app/checklists/queries/getChecklist"
+import deleteChecklist from "src/app/checklists/mutations/deleteChecklist"
export const Checklist = () => {
const router = useRouter()
@@ -26,7 +26,9 @@ export const Checklist = () => {