Skip to content

Commit

Permalink
Initial commit from Create Next App
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianostankewicz committed Sep 24, 2024
0 parents commit 77cccf0
Show file tree
Hide file tree
Showing 27 changed files with 1,429 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Redux Toolkit TypeScript Example

This example shows how to integrate Next.js with [Redux Toolkit](https://redux-toolkit.js.org).

**Redux Toolkit**(also known as "RTK" for short) provides a standardized way to write Redux logic. It includes utilities that help simplify many common use cases, including [store setup](https://redux-toolkit.js.org/api/configureStore), [creating reducers and writing immutable update logic](https://redux-toolkit.js.org/api/createreducer), and even [creating entire "slices" of state at once](https://redux-toolkit.js.org/api/createslice). This example showcases each of these features in conjunction with Next.js.

## Deploy Your Own

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-redux&project-name=with-redux&repository-name=with-redux)

## How to Use

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:

```bash
npx create-next-app --example with-redux with-redux-app
```

```bash
yarn create next-app --example with-redux with-redux-app
```

```bash
pnpm create next-app --example with-redux with-redux-app
```

Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
31 changes: 31 additions & 0 deletions app/StoreProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";
import type { AppStore } from "@/lib/store";
import { makeStore } from "@/lib/store";
import { setupListeners } from "@reduxjs/toolkit/query";
import type { ReactNode } from "react";
import { useEffect, useRef } from "react";
import { Provider } from "react-redux";

interface Props {
readonly children: ReactNode;
}

export const StoreProvider = ({ children }: Props) => {
const storeRef = useRef<AppStore | null>(null);

if (!storeRef.current) {
// Create the store instance the first time this renders
storeRef.current = makeStore();
}

useEffect(() => {
if (storeRef.current != null) {
// configure listeners using the provided defaults
// optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
const unsubscribe = setupListeners(storeRef.current.dispatch);
return unsubscribe;
}
}, []);

return <Provider store={storeRef.current}>{children}</Provider>;
};
16 changes: 16 additions & 0 deletions app/api/counter/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

interface Context {
params: undefined;
}

export async function POST(request: NextRequest, context: Context) {
const body: { amount: number } = await request.json();
const { amount = 1 } = body;

// simulate IO latency
await new Promise((resolve) => setTimeout(resolve, 500));

return NextResponse.json({ data: amount });
}
37 changes: 37 additions & 0 deletions app/components/Nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";

import styles from "../styles/layout.module.css";

export const Nav = () => {
const pathname = usePathname();

return (
<nav className={styles.nav}>
<Link
className={`${styles.link} ${pathname === "/" ? styles.active : ""}`}
href="/"
>
Home
</Link>
<Link
className={`${styles.link} ${
pathname === "/verify" ? styles.active : ""
}`}
href="/verify"
>
Verify
</Link>
<Link
className={`${styles.link} ${
pathname === "/quotes" ? styles.active : ""
}`}
href="/quotes"
>
Quotes
</Link>
</nav>
);
};
81 changes: 81 additions & 0 deletions app/components/counter/Counter.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
.row {
display: flex;
align-items: center;
justify-content: center;
}

.row > button {
margin-left: 4px;
margin-right: 8px;
}

.row:not(:last-child) {
margin-bottom: 16px;
}

.value {
font-size: 78px;
padding-left: 16px;
padding-right: 16px;
margin-top: 2px;
font-family: "Courier New", Courier, monospace;
}

.button {
appearance: none;
background: none;
font-size: 32px;
padding-left: 12px;
padding-right: 12px;
outline: none;
border: 2px solid transparent;
color: rgb(112, 76, 182);
padding-bottom: 4px;
cursor: pointer;
background-color: rgba(112, 76, 182, 0.1);
border-radius: 2px;
transition: all 0.15s;
}

.textbox {
font-size: 32px;
padding: 2px;
width: 64px;
text-align: center;
margin-right: 4px;
}

.button:hover,
.button:focus {
border: 2px solid rgba(112, 76, 182, 0.4);
}

.button:active {
background-color: rgba(112, 76, 182, 0.2);
}

.asyncButton {
composes: button;
position: relative;
}

.asyncButton:after {
content: "";
background-color: rgba(112, 76, 182, 0.15);
display: block;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
transition:
width 1s linear,
opacity 0.5s ease 1s;
}

.asyncButton:active:after {
width: 0%;
opacity: 1;
transition: 0s;
}
81 changes: 81 additions & 0 deletions app/components/counter/Counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"use client";

import { useState } from "react";

import {
decrement,
increment,
incrementAsync,
incrementByAmount,
incrementIfOdd,
selectCount,
selectStatus,
} from "@/lib/features/counter/counterSlice";

import { useAppDispatch, useAppSelector } from "@/lib/hooks";
import styles from "./Counter.module.css";

export const Counter = () => {
const dispatch = useAppDispatch();
const count = useAppSelector(selectCount);
const status = useAppSelector(selectStatus);
const [incrementAmount, setIncrementAmount] = useState("2");

const incrementValue = Number(incrementAmount) || 0;

return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span aria-label="Count" className={styles.value}>
{count}
</span>
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
<div className={styles.row}>
<input
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
type="number"
onChange={(e) => {
setIncrementAmount(e.target.value);
}}
/>
<button
className={styles.button}
onClick={() => dispatch(incrementByAmount(incrementValue))}
>
Add Amount
</button>
<button
className={styles.asyncButton}
disabled={status !== "idle"}
onClick={() => dispatch(incrementAsync(incrementValue))}
>
Add Async
</button>
<button
className={styles.button}
onClick={() => {
dispatch(incrementIfOdd(incrementValue));
}}
>
Add If Odd
</button>
</div>
</div>
);
};
20 changes: 20 additions & 0 deletions app/components/quotes/Quotes.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.select {
font-size: 25px;
padding: 5px;
padding-top: 2px;
padding-bottom: 2px;
size: 50;
outline: none;
border: 2px solid transparent;
color: rgb(112, 76, 182);
cursor: pointer;
background-color: rgba(112, 76, 182, 0.1);
border-radius: 5px;
transition: all 0.15s;
}

.container {
display: flex;
flex-direction: column;
align-items: center;
}
60 changes: 60 additions & 0 deletions app/components/quotes/Quotes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client";
import { useGetQuotesQuery } from "@/lib/features/quotes/quotesApiSlice";
import { useState } from "react";
import styles from "./Quotes.module.css";

const options = [5, 10, 20, 30];

export const Quotes = () => {
const [numberOfQuotes, setNumberOfQuotes] = useState(10);
// Using a query hook automatically fetches data and returns query values
const { data, isError, isLoading, isSuccess } =
useGetQuotesQuery(numberOfQuotes);

if (isError) {
return (
<div>
<h1>There was an error!!!</h1>
</div>
);
}

if (isLoading) {
return (
<div>
<h1>Loading...</h1>
</div>
);
}

if (isSuccess) {
return (
<div className={styles.container}>
<h3>Select the Quantity of Quotes to Fetch:</h3>
<select
className={styles.select}
value={numberOfQuotes}
onChange={(e) => {
setNumberOfQuotes(Number(e.target.value));
}}
>
{options.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
{data.quotes.map(({ author, quote, id }) => (
<blockquote key={id}>
&ldquo;{quote}&rdquo;
<footer>
<cite>{author}</cite>
</footer>
</blockquote>
))}
</div>
);
}

return null;
};
Binary file added app/icon.ico
Binary file not shown.
Loading

0 comments on commit 77cccf0

Please sign in to comment.