19 |
20 | {children}
21 |
22 | );
23 | }
24 |
25 | export default Button;
26 |
--------------------------------------------------------------------------------
/features/products/productSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | searchTerm: "",
5 | categoryTerm: "",
6 | };
7 |
8 | const productSlice = createSlice({
9 | name: "product",
10 | initialState,
11 | reducers: {
12 | setSearchTerm: (state, action) => {
13 | state.categoryTerm = "";
14 | state.searchTerm = action.payload;
15 | },
16 | setCategoryTerm: (state, action) => {
17 | state.searchTerm = "";
18 | state.categoryTerm = action.payload;
19 | },
20 | },
21 | });
22 |
23 | export const { setSearchTerm, setCategoryTerm } = productSlice.actions;
24 | export default productSlice.reducer;
25 |
--------------------------------------------------------------------------------
/features/users/userSlice.tsx:
--------------------------------------------------------------------------------
1 | import { Filter } from "@/global-interfaces";
2 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
3 |
4 | const initialState = {
5 | filter: {
6 | type: "search", // default filter type
7 | value: "", // default filter value
8 | },
9 | };
10 |
11 | const userSlice = createSlice({
12 | name: "user",
13 | initialState,
14 | reducers: {
15 | setFilter: (state, action: PayloadAction
) => {
16 | state.filter.type = action.payload.type;
17 | state.filter.value = action.payload.value;
18 | },
19 | },
20 | });
21 |
22 | export const { setFilter } = userSlice.actions;
23 | export default userSlice.reducer;
24 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
5 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./features/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [
19 | require("@tailwindcss/typography"),
20 | require("@shrutibalasa/tailwind-grid-auto-fit"),
21 | require("daisyui"),
22 | ],
23 | daisyui: {
24 | themes: ["light", "dark"],
25 | styled: true,
26 | rtl: false,
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/features/products/apiProducts.ts:
--------------------------------------------------------------------------------
1 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
2 |
3 | export const apiProducts = createApi({
4 | reducerPath: "apiProducts",
5 | baseQuery: fetchBaseQuery({
6 | baseUrl: "https://dummyjson.com",
7 | }),
8 | endpoints: (builder) => ({
9 | getProducts: builder.query({
10 | query: ({ searchTerm, categoryTerm }) => {
11 | if (searchTerm) {
12 | return `products/search?q=${searchTerm}`;
13 | }
14 | if (categoryTerm) {
15 | return `products/category/${categoryTerm}`;
16 | }
17 | return `products?limit=10`;
18 | },
19 | }),
20 | getCategories: builder.query({
21 | query: () => `products/categories`,
22 | }),
23 | }),
24 | });
25 |
26 | export const { useGetProductsQuery, useGetCategoriesQuery } = apiProducts;
27 |
--------------------------------------------------------------------------------
/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode, useContext } from "react";
2 | import { Footer, Footerbar, Main, Navbar } from "./layouts";
3 | import { ThemeContext } from "@/contexts/ThemeContext";
4 |
5 | interface LayoutProps {
6 | children: ReactNode;
7 | }
8 |
9 | function Layout({ children }: LayoutProps) {
10 | const { theme } = useContext(ThemeContext);
11 |
12 | return (
13 | <>
14 |
15 |
16 |
17 | {children
18 | ? children
19 | : "This is a Layout container. Must have children"}
20 |
21 |
24 |
25 | >
26 | );
27 | }
28 |
29 | export default Layout;
30 |
--------------------------------------------------------------------------------
/components/ui-ux/ProductSearchBar.tsx:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux";
2 | import { setSearchTerm } from "@/features/products/productSlice";
3 | import { RootState } from "@/global-interfaces";
4 |
5 | export const ProductSearchBar = () => {
6 | const dispatch = useDispatch();
7 | const searchTerm = useSelector(
8 | (state: RootState) => state.product.searchTerm
9 | );
10 |
11 | const handleInputChange = (event: React.ChangeEvent) => {
12 | dispatch(setSearchTerm(event.target.value));
13 | };
14 |
15 | return (
16 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/global-interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface RootState {
2 | product: {
3 | searchTerm: string;
4 | categoryTerm: string;
5 | };
6 | user: {
7 | filter: Filter;
8 | };
9 | }
10 |
11 | export interface Filter {
12 | type: "search" | "card" | "currency" | "gender";
13 | value: string;
14 | }
15 |
16 | export interface ProductsData {
17 | id: number;
18 | title: string;
19 | description: string;
20 | thumbnail: string;
21 | category: string;
22 | brand: string;
23 | }
24 |
25 | export interface User {
26 | id: number;
27 | firstName: string;
28 | lastName: string;
29 | image: string;
30 | gender: string;
31 | bank: Bank;
32 | email: string;
33 | phone: string;
34 | address: Address;
35 | university: string;
36 | username: string;
37 | }
38 |
39 | export interface Bank {
40 | cardType: string;
41 | currency: string;
42 | }
43 |
44 | export interface Address {
45 | city: string;
46 | state: string;
47 | }
48 |
--------------------------------------------------------------------------------
/components/layouts/Container.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | interface Props {
4 | children: ReactNode;
5 | className: string;
6 | FULL: boolean;
7 | }
8 |
9 | function Container({ children, className, FULL }: Props) {
10 | return (
11 | <>
12 | {FULL && (
13 |
14 | {children
15 | ? children
16 | : "This is a Container container. Must have children"}
17 |
18 | )}
19 | {!FULL && (
20 |
21 | {children
22 | ? children
23 | : "This is a Container container. Must have children"}
24 |
25 | )}
26 | {/* LG: 1024+ IS SET TO 91% WIDTH (w-11/12) */}
27 | {/* XL: 1280+ IS SET TO 80% WIDTH (w-4/5) */}
28 | >
29 | );
30 | }
31 |
32 | export default Container;
33 |
--------------------------------------------------------------------------------
/store/store.ts:
--------------------------------------------------------------------------------
1 | import { apiPost } from "@/features/posts/apiPosts";
2 | import { apiProducts } from "@/features/products/apiProducts";
3 | import { apiUsers } from "@/features/users/apiUsers";
4 | import { configureStore } from "@reduxjs/toolkit";
5 | import { setupListeners } from "@reduxjs/toolkit/query";
6 | import productSliceReducer from "@/features/products/productSlice";
7 | import userSliceReducer from "@/features/users/userSlice";
8 |
9 | export const store = configureStore({
10 | reducer: {
11 | [apiPost.reducerPath]: apiPost.reducer,
12 | [apiProducts.reducerPath]: apiProducts.reducer,
13 | [apiUsers.reducerPath]: apiUsers.reducer,
14 | product: productSliceReducer,
15 | user: userSliceReducer,
16 | },
17 | middleware: (getDefaultMiddleware) =>
18 | getDefaultMiddleware().concat([
19 | apiPost.middleware,
20 | apiProducts.middleware,
21 | apiUsers.middleware,
22 | ]),
23 | });
24 |
25 | setupListeners(store.dispatch);
26 |
--------------------------------------------------------------------------------
/components/ui-ux/UserSearchBar.tsx:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux";
2 | import { setFilter } from "@/features/users/userSlice";
3 | import { RootState } from "@/global-interfaces";
4 |
5 | export const UserSearchBar = () => {
6 | const dispatch = useDispatch();
7 | const filter = useSelector((state: RootState) => state.user.filter);
8 | const searchTerm = filter.type === "search" ? filter.value : "";
9 |
10 | const handleInputChange = (event: React.ChangeEvent) => {
11 | // For setting a search term
12 | dispatch(setFilter({ type: "search", value: event.target.value }));
13 | };
14 |
15 | return (
16 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/components/ui-ux/Contact.scss:
--------------------------------------------------------------------------------
1 | form {
2 | display: flex;
3 | align-items: flex-start;
4 | flex-direction: column;
5 | width: 100%;
6 | font-size: 16px;
7 |
8 | input {
9 | width: 100%;
10 | height: 35px;
11 | padding: 7px;
12 | outline: none;
13 | border-radius: 5px;
14 | border: 1px solid rgb(220, 220, 220);
15 |
16 | &:focus {
17 | border: 2px solid rgba(0, 206, 158, 1);
18 | }
19 | }
20 |
21 | textarea {
22 | max-width: 100%;
23 | min-width: 100%;
24 | width: 100%;
25 | max-height: 100px;
26 | min-height: 100px;
27 | padding: 7px;
28 | outline: none;
29 | border-radius: 5px;
30 | border: 1px solid rgb(220, 220, 220);
31 |
32 | &:focus {
33 | border: 2px solid rgba(0, 206, 158, 1);
34 | }
35 | }
36 |
37 | label {
38 | margin-top: 1rem;
39 | }
40 |
41 | input[type='submit'] {
42 | margin-top: 2rem;
43 | cursor: pointer;
44 | background: rgb(249, 105, 14);
45 | color: white;
46 | border: none;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/components/ui-ux/RadioButtonCategoryGroup.tsx:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { setCategoryTerm } from "@/features/products/productSlice";
3 | import { useGetCategoriesQuery } from "@/features/products/apiProducts";
4 |
5 | export const RadioButtonCategoryGroup = () => {
6 | const dispatch = useDispatch();
7 | const { data: categories } = useGetCategoriesQuery({});
8 |
9 | const handleCategoryChange = (event: React.ChangeEvent) => {
10 | dispatch(setCategoryTerm(event.target.value));
11 | };
12 |
13 | return (
14 |
15 | {categories?.map((category: string[], index: number) => (
16 |
17 |
24 | {category}
25 |
26 | ))}
27 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "starter-next13-pages-ts-v1",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@emailjs/browser": "^3.11.0",
13 | "@heroicons/react": "^2.0.18",
14 | "@reduxjs/toolkit": "^1.9.5",
15 | "@types/node": "20.4.2",
16 | "@types/react": "18.2.15",
17 | "@types/react-dom": "18.2.7",
18 | "autoprefixer": "10.4.14",
19 | "axios": "^1.4.0",
20 | "daisyui": "^3.2.1",
21 | "heroicons": "^2.0.18",
22 | "next": "13.4.10",
23 | "next-redux-wrapper": "^8.1.0",
24 | "postcss": "8.4.26",
25 | "qs": "^6.11.2",
26 | "react": "18.2.0",
27 | "react-dom": "18.2.0",
28 | "react-icons": "^4.10.1",
29 | "react-redux": "^8.1.1",
30 | "redux": "^4.2.1",
31 | "sass": "^1.63.6",
32 | "tailwindcss": "3.3.3",
33 | "typescript": "5.1.6"
34 | },
35 | "devDependencies": {
36 | "@shrutibalasa/tailwind-grid-auto-fit": "^1.1.0",
37 | "@tailwindcss/typography": "^0.5.9",
38 | "@types/qs": "^6.9.7"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/features/users/apiUsers.ts:
--------------------------------------------------------------------------------
1 | import { Filter } from "@/global-interfaces";
2 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
3 |
4 | const dataSelect =
5 | "select=firstName,lastName,image,gender,bank,university,address,university,username";
6 |
7 | export const apiUsers = createApi({
8 | reducerPath: "apiUsers",
9 | baseQuery: fetchBaseQuery({
10 | baseUrl: "https://dummyjson.com",
11 | }),
12 | endpoints: (builder) => ({
13 | getUsers: builder.query({
14 | query: ({ filter }: { filter: Filter }) => {
15 | switch (filter.type) {
16 | case "search":
17 | return `users/search?q=${filter.value}`;
18 | case "card":
19 | return `users/filter?key=bank.cardType&value=${filter.value}&${dataSelect}`;
20 | case "currency":
21 | return `users/filter?key=bank.currency&value=${filter.value}&${dataSelect}`;
22 | case "gender":
23 | return `users/filter?key=gender&value=${filter.value}&${dataSelect}`;
24 | default:
25 | return `users?limit=8&${dataSelect}`;
26 | }
27 | },
28 | }),
29 | }),
30 | });
31 |
32 | export const { useGetUsersQuery } = apiUsers;
33 |
--------------------------------------------------------------------------------
/archive/userSlice-1-keep.tsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | searchTerm: "",
5 | cardTerm: "",
6 | currencyTerm: "",
7 | genderTerm: "",
8 | };
9 |
10 | const userSlice = createSlice({
11 | name: "user",
12 | initialState,
13 | reducers: {
14 | setSearchTerm: (state, action) => {
15 | state.cardTerm = "";
16 | state.currencyTerm = "";
17 | state.genderTerm = "";
18 | state.searchTerm = action.payload;
19 | },
20 | setCardTerm: (state, action) => {
21 | state.currencyTerm = "";
22 | state.genderTerm = "";
23 | state.searchTerm = "";
24 | state.cardTerm = action.payload;
25 | },
26 | setCurrencyTerm: (state, action) => {
27 | state.genderTerm = "";
28 | state.searchTerm = "";
29 | state.cardTerm = "";
30 | state.currencyTerm = action.payload;
31 | },
32 | setGenderTerm: (state, action) => {
33 | state.searchTerm = "";
34 | state.cardTerm = "";
35 | state.currencyTerm = "";
36 | state.genderTerm = action.payload;
37 | },
38 | },
39 | });
40 |
41 | export const { setSearchTerm, setCardTerm, setCurrencyTerm, setGenderTerm } =
42 | userSlice.actions;
43 | export default userSlice.reducer;
44 |
--------------------------------------------------------------------------------
/components/ui-ux/RadioGroupGender.tsx:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { setFilter } from "@/features/users/userSlice";
3 |
4 | export const RadioGroupGender = () => {
5 | const dispatch = useDispatch();
6 | const genders = [
7 | {
8 | id: 1,
9 | name: "Male",
10 | slug: "male",
11 | },
12 | {
13 | id: 2,
14 | name: "Female",
15 | slug: "female",
16 | },
17 | ];
18 | const handleGenderChange = (event: React.ChangeEvent) => {
19 | console.log("GenderTerm in Radio:", event.target.value);
20 |
21 | // For setting a gender term
22 | dispatch(setFilter({ type: "gender", value: event.target.value }));
23 | };
24 |
25 | return (
26 |
27 |
Filter by Gender:
28 | {genders?.map((gender) => (
29 |
30 |
37 | {gender.name}
38 |
39 | ))}
40 |
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/components/ui-ux/ListGroup.tsx:
--------------------------------------------------------------------------------
1 | import { MouseEvent, useState } from "react";
2 | import "./ListGroup.scss";
3 | // import "bootstrap/dist/css/bootstrap.css";
4 |
5 | interface Props {
6 | items: string[];
7 | heading: string;
8 | onSelectItem: (item: string) => void;
9 | }
10 |
11 | function ListGroup({ items, heading, onSelectItem }: Props) {
12 | const [selectedIndex, setSelectedIndex] = useState(0);
13 |
14 | // items = [];
15 |
16 | // Bootstrap like classes added for the sake of mosh
17 | return (
18 | <>
19 | {heading}
20 | {!items.length && (
21 | <>
22 | No items found
23 | >
24 | )}
25 |
26 | {items.map((city, index) => (
27 | {
35 | setSelectedIndex(index);
36 | onSelectItem(city);
37 | }}
38 | >
39 | {city}
40 |
41 | ))}
42 |
43 | >
44 | );
45 | }
46 |
47 | export default ListGroup;
48 |
--------------------------------------------------------------------------------
/components/ui-ux/Contact.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, FormEvent } from "react";
2 | import emailjs from "@emailjs/browser";
3 | import "./Contact.scss";
4 |
5 | // npm i @emailjs/browser
6 |
7 | const Contact = () => {
8 | const form = useRef(null);
9 |
10 | const sendEmail = (e: FormEvent) => {
11 | e.preventDefault();
12 |
13 | if (form.current !== null) {
14 | emailjs
15 | .sendForm(
16 | "service_yfdtacg", // SERVICE ID
17 | "template_r7i0nqw", // TEMPLATE ID
18 | form.current,
19 | "WGdP1_0dhm0s_-PSD" // PUBLIC KEY
20 | )
21 | .then(
22 | (result) => {
23 | console.log(result.text);
24 | console.log("message sent");
25 | },
26 | (error) => {
27 | console.log(error.text);
28 | }
29 | );
30 | }
31 | };
32 |
33 | return (
34 | <>
35 |
44 | >
45 | );
46 | };
47 |
48 | export default Contact;
49 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/data-layer/strapi/contacts.ts:
--------------------------------------------------------------------------------
1 | import axios from "../apiClient";
2 | import qs from "qs";
3 |
4 | // import { companyReducer } from './utils';
5 |
6 | const apiUrl = process.env.STRAPI_API_BASE_URL;
7 |
8 | interface Locations {
9 | locationIds: string[];
10 | }
11 |
12 | export interface Contact {
13 | firstName: string;
14 | lastName: string;
15 | email: string;
16 | phone: string;
17 | roles: {
18 | id: number;
19 | type: string;
20 | role: string;
21 | locations: Locations;
22 | };
23 | }
24 |
25 | export interface ContactData {
26 | id: number;
27 | attributes: Contact;
28 | }
29 |
30 | export interface ContactSingleData {
31 | data: {
32 | id: number;
33 | attributes: Contact;
34 | };
35 | meta: object;
36 | }
37 |
38 | interface Pagination {
39 | page: number;
40 | pageSize: number;
41 | pageCount: number;
42 | total: number;
43 | }
44 |
45 | export interface ApiResponse {
46 | data: ContactData[];
47 | meta: {
48 | pagination: Pagination;
49 | };
50 | }
51 |
52 | export const getContacts = async (): Promise => {
53 | const query = qs.stringify(
54 | {
55 | populate: ["roles"],
56 | },
57 | {
58 | encodeValuesOnly: true,
59 | }
60 | );
61 | const res = await axios.get(`${apiUrl}/contacts?${query}`);
62 | const rawCompanies = res.data.data;
63 | return rawCompanies;
64 | };
65 |
--------------------------------------------------------------------------------
/pages/template/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { Inter } from "next/font/google";
3 | import Head from "next/head";
4 | import Layout from "@/components/Layout";
5 | import { Box, Container, Row } from "@/components/layouts";
6 |
7 | const inter = Inter({ subsets: ["latin"] });
8 |
9 | export default function PageTemplate() {
10 | return (
11 |
12 |
13 | Next Page Template
14 |
15 |
16 | {/* */}
17 |
18 |
19 | This is the Page Template (Copy Me)
20 |
21 | Lorem ipsum dolor, sit amet consectetur adipisicing elit
22 |
23 |
24 | Lorem ipsum dolor, sit amet consectetur adipisicing elit
25 |
26 |
27 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Est
28 | molestias pariatur earum praesentium tempore natus asperiores alias
29 | facere delectus ullam? At in ducimus et delectus, autem veniam quas
30 | natus quam?
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/pages/blog/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { Inter } from "next/font/google";
3 | import Head from "next/head";
4 | import Layout from "@/components/Layout";
5 | import { Box, Container, Row } from "@/components/layouts";
6 | import Spinner from "@/components/ui-ux/Spinner";
7 | import { useGetPostsQuery } from "@/features/posts/apiPosts";
8 | import Posts, { PostData } from "@/features/posts/Posts";
9 |
10 | const inter = Inter({ subsets: ["latin"] });
11 |
12 | export default function Blog() {
13 | const { data, isLoading, error } = useGetPostsQuery({});
14 | // console.log("Posts:", data?.posts);
15 | const posts: PostData[] = data?.posts;
16 |
17 | return (
18 |
19 |
20 | Next Page Template
21 |
22 |
23 | {/* */}
24 |
25 |
26 | The Blog
27 | Blog with RTK Query on Next.js
28 |
29 |
30 | {isLoading && }
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/components/ui-ux/RadioGroupCreditCard.tsx:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { setFilter } from "@/features/users/userSlice";
3 |
4 | export const RadioGroupCreditCard = () => {
5 | const dispatch = useDispatch();
6 | const cards = [
7 | {
8 | id: 1,
9 | name: "Visa",
10 | slug: "visa",
11 | },
12 | {
13 | id: 2,
14 | name: "Master Card",
15 | slug: "mastercard",
16 | },
17 | {
18 | id: 3,
19 | name: "JCB",
20 | slug: "jcb",
21 | },
22 | {
23 | id: 4,
24 | name: "American Express",
25 | slug: "americanexpress",
26 | },
27 | ];
28 | const handleCardChange = (event: React.ChangeEvent) => {
29 | console.log("CardTerm in Radio:", event.target.value);
30 |
31 | // For setting a card term
32 | dispatch(setFilter({ type: "card", value: event.target.value }));
33 | };
34 |
35 | return (
36 |
37 |
Filter by Credit Cards:
38 |
39 | {cards?.map((card) => (
40 |
41 |
48 | {card.name}
49 |
50 | ))}
51 |
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/archive/apiUsers-1-keep.ts:
--------------------------------------------------------------------------------
1 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
2 |
3 | const dataSelect =
4 | "select=firstName,lastName,image,gender,bank,university,address,university,username";
5 |
6 | export const apiUsers = createApi({
7 | reducerPath: "apiUsers",
8 | baseQuery: fetchBaseQuery({
9 | baseUrl: "https://dummyjson.com",
10 | }),
11 | endpoints: (builder) => ({
12 | getUsers: builder.query({
13 | query: ({ searchTerm, cardTerm, currencyTerm, genderTerm }) => {
14 | if (searchTerm) {
15 | return `users/search?q=${searchTerm}`;
16 | }
17 |
18 | if (currencyTerm) {
19 | console.log("CurrencyTerm in api:", currencyTerm);
20 |
21 | // -- Dollar, Euro, Peso, Rial, Ringgit
22 | return `users/filter?key=bank.currency&value=${currencyTerm}&${dataSelect}`;
23 | }
24 |
25 | if (genderTerm) {
26 | console.log("GenderTerm in api:", genderTerm);
27 |
28 | // -- male/female
29 | return `users/filter?key=gender&value=${genderTerm}&${dataSelect}`;
30 | }
31 |
32 | if (cardTerm) {
33 | console.log("CardTerm in api:", cardTerm);
34 | // -- visa, mastercard, jcb, bankcard, americanexpress
35 | return `users/filter?key=bank.cardType&value=${cardTerm}&${dataSelect}`;
36 | }
37 |
38 | return `users?limit=8&${dataSelect}`;
39 | },
40 | }),
41 | }),
42 | });
43 |
44 | export const { useGetUsersQuery } = apiUsers;
45 |
--------------------------------------------------------------------------------
/pages/blog/testPage.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { Row } from "@/components/layouts";
3 | import Container from "@/components/layouts/Container";
4 | import { apiPost } from "@/features/posts/apiPosts";
5 | import { store } from "@/store/store";
6 |
7 | export async function getStaticProps() {
8 | const { dispatch, getState } = store;
9 |
10 | await dispatch(apiPost.endpoints.getPosts.initiate({}));
11 |
12 | // It's important to `getState` after `dispatch` finishes
13 | const state = getState();
14 | console.log("RTK State:", state);
15 |
16 | // CANNOT FIND posts INSIDE apiPost ...
17 | // const posts = state.apiPost.queries.getPosts();
18 | // const posts = [
19 | // {
20 | // title: "My Test 1",
21 | // id: 1,
22 | // },
23 | // ];
24 |
25 | // return { props: { posts } };
26 | }
27 |
28 | interface Post {
29 | title: string;
30 | id: number;
31 | }
32 | interface Posts {
33 | posts: Post[];
34 | }
35 |
36 | export default function TestPage({ posts }: Posts) {
37 | return (
38 |
39 |
40 |
41 | The Test Page
42 | Testing RTK
43 |
44 |
45 |
Posts
46 | {posts.map((post) => (
47 |
{post.title}
48 | ))}
49 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/components/ui-ux/RadioGroupCurrency.tsx:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { setFilter } from "@/features/users/userSlice";
3 |
4 | export const RadioGroupCurrency = () => {
5 | const dispatch = useDispatch();
6 | const currencies = [
7 | {
8 | id: 1,
9 | name: "Dollar",
10 | slug: "dollar",
11 | },
12 | {
13 | id: 2,
14 | name: "Euro",
15 | slug: "euro",
16 | },
17 | {
18 | id: 3,
19 | name: "Peso",
20 | slug: "peso",
21 | },
22 | {
23 | id: 4,
24 | name: "Rial",
25 | slug: "rial",
26 | },
27 |
28 | {
29 | id: 5,
30 | name: "Ringgit",
31 | slug: "ringgit",
32 | },
33 | ];
34 | const handleCurrencyChange = (event: React.ChangeEvent) => {
35 | console.log("CurrencyTerm in Radio:", event.target.value);
36 |
37 | // For setting a currency term
38 | dispatch(setFilter({ type: "currency", value: event.target.value }));
39 | };
40 |
41 | return (
42 |
43 |
Filter by Currency:
44 |
45 | {currencies?.map((currency) => (
46 |
47 |
54 | {currency.name}
55 |
56 | ))}
57 |
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/components/ui-ux/Spinner.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Spinner() {
4 | return (
5 |
6 |
13 |
17 |
21 |
22 |
Loading...
23 |
24 | );
25 | }
26 |
27 | export default Spinner;
28 |
--------------------------------------------------------------------------------
/pages/products/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Head from "next/head";
3 | import Layout from "@/components/Layout";
4 | import { Box, Container, Row } from "@/components/layouts";
5 | import Spinner from "@/components/ui-ux/Spinner";
6 | import { useGetProductsQuery } from "@/features/products/apiProducts";
7 | import Products from "@/features/products/Products";
8 | import { useSelector } from "react-redux";
9 | import { ProductsData, RootState } from "@/global-interfaces";
10 | import { ProductSearchBar } from "@/components/ui-ux/ProductSearchBar";
11 | import { RadioButtonCategoryGroup } from "@/components/ui-ux/RadioButtonCategoryGroup";
12 |
13 | export default function ProductsPage() {
14 | const searchTerm = useSelector(
15 | (state: RootState) => state.product.searchTerm
16 | );
17 | const categoryTerm = useSelector(
18 | (state: RootState) => state.product.categoryTerm
19 | );
20 |
21 | const { data, isLoading, error } = useGetProductsQuery({
22 | searchTerm,
23 | categoryTerm,
24 | });
25 | const products: ProductsData[] = data?.products;
26 |
27 | console.log("Products:", data);
28 |
29 | return (
30 |
31 |
32 | Next Redux Template
33 |
34 |
35 |
36 |
37 | The Products
38 |
39 |
40 |
41 |
42 | {isLoading && }
43 |
44 |
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # starter-next13-pages-ts-v1
2 |
3 | Next.js 13 Starter kit with pages dir w/ Typescript. App router is not used due to instability. Tailwind + Redux Toolkit
4 |
5 | ## Getting Started
6 |
7 | First, run the development server:
8 |
9 | ```bash
10 | npm run dev
11 | # or
12 | yarn dev
13 | # or
14 | pnpm dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
20 |
21 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
22 |
23 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
24 |
25 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
26 |
27 | ## Learn More
28 |
29 | To learn more about Next.js, take a look at the following resources:
30 |
31 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
32 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
33 |
34 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
35 |
36 | ## Deploy on Vercel
37 |
38 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
39 |
40 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
41 |
--------------------------------------------------------------------------------
/pages/users/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { Inter } from "next/font/google";
3 | import Head from "next/head";
4 | import Layout from "@/components/Layout";
5 | import { Box, Container, Row } from "@/components/layouts";
6 | import Link from "next/link";
7 | import Spinner from "@/components/ui-ux/Spinner";
8 | import { UserSearchBar } from "@/components/ui-ux/UserSearchBar";
9 | import { RadioGroupGender } from "@/components/ui-ux/RadioGroupGender";
10 | import { RadioGroupCreditCard } from "@/components/ui-ux/RadioGroupCreditCard";
11 | import { RadioGroupCurrency } from "@/components/ui-ux/RadioGroupCurrency";
12 | import { useGetUsersQuery } from "@/features/users/apiUsers";
13 | import Users from "@/features/users/Users";
14 | import { RootState, User } from "@/global-interfaces";
15 | import { useSelector } from "react-redux";
16 |
17 | const UsersPage = () => {
18 | const filter = useSelector((state: RootState) => state.user.filter);
19 |
20 | const { data, isLoading } = useGetUsersQuery({ filter });
21 | const clients: User[] = data?.users;
22 | // console.log("Users Data", data);
23 |
24 | return (
25 |
26 |
27 | Next Redux Template
28 |
29 |
30 |
31 |
32 | The Robot List
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {isLoading && }
42 |
43 |
44 |
45 |
46 | );
47 | };
48 |
49 | export default UsersPage;
50 |
--------------------------------------------------------------------------------
/pages/contacts/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { Inter } from "next/font/google";
3 | import Head from "next/head";
4 | import Layout from "@/components/Layout";
5 | import { Box, Container, Row } from "@/components/layouts";
6 | import datasource from "@/data-layer";
7 | import { ContactData } from "@/data-layer/strapi/contacts";
8 |
9 | const inter = Inter({ subsets: ["latin"] });
10 |
11 | export const getStaticProps = async () => {
12 | if (!datasource.getContacts) {
13 | // handle the error, perhaps by throwing it
14 | throw new Error("Data source not configured correctly");
15 | }
16 |
17 | const contacts = await datasource.getContacts();
18 | // console.log("Contacts: ", contacts);
19 | console.dir(contacts, { depth: null });
20 |
21 | return {
22 | props: {
23 | contacts,
24 | },
25 | revalidate: 5,
26 | };
27 | };
28 |
29 | export default function ContactListPage({
30 | contacts,
31 | }: {
32 | contacts: ContactData[];
33 | }) {
34 | return (
35 |
36 |
37 | Next Page Template
38 |
39 |
40 | {/* */}
41 |
42 |
43 |
44 | This is the Contacts Page
45 | This is done with:
46 |
47 |
48 | Axios apiClient w/ auth header including Strapi v4 Local Token
49 |
50 | Data layer:
51 |
52 | /strapi
53 | contact.ts
54 | company.ts
55 | index.ts (has the datasource)
56 |
57 |
58 |
59 |
60 |
61 | {contacts.map((contact) => (
62 | {contact.attributes.firstName}
63 | ))}
64 |
65 |
66 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/features/posts/Posts.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@/components/layouts";
2 | import React from "react";
3 |
4 | export interface PostData {
5 | id: number;
6 | title: string;
7 | body: string;
8 | tags: string[];
9 | }
10 |
11 | const Posts = ({ posts }: { posts: PostData[] }) => {
12 | return (
13 |
14 | {posts &&
15 | posts?.map((post) => (
16 |
20 |
21 |
22 | {"July 20, 2023"}
23 |
24 | {post?.tags?.map((tag) => (
25 |
29 | {tag}
30 |
31 | ))}
32 |
33 |
34 |
40 |
41 | {post.body}
42 |
43 |
44 |
45 |
50 |
59 |
60 |
61 | ))}
62 |
63 | );
64 | };
65 |
66 | export default Posts;
67 |
--------------------------------------------------------------------------------
/features/products/Products.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@/components/layouts";
2 | import { ProductsData } from "@/global-interfaces";
3 | import React from "react";
4 |
5 | const Products = ({ products }: { products: ProductsData[] }) => {
6 | return (
7 | //
8 |
9 | {products &&
10 | products?.map((products) => (
11 |
15 |
16 |
17 | {"July 20, 2023"}
18 |
19 |
20 |
21 | {products.category}
22 |
23 |
24 |
25 |
31 |
32 | {products.description}
33 |
34 |
35 |
36 |
41 |
42 |
55 |
56 | ))}
57 |
58 | );
59 | };
60 |
61 | export default Products;
62 |
--------------------------------------------------------------------------------
/pages/daisyui/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { Inter } from "next/font/google";
3 | import Head from "next/head";
4 | import Layout from "@/components/Layout";
5 | import { Box, Container, Row } from "@/components/layouts";
6 |
7 | const inter = Inter({ subsets: ["latin"] });
8 |
9 | export default function DaisyUi() {
10 | return (
11 |
12 |
13 | Next DaisyUi
14 |
15 |
16 |
17 |
18 | Testing Daisy UI Buttons
19 |
20 | Button
21 | Button
22 | Button
23 | Button
24 | Button
25 | Button
26 |
27 |
28 |
29 | Testing Daisy UI Buttons
30 |
31 | Button
32 | Button
33 | Button
34 | Button
35 |
36 |
37 |
38 | Testing Daisy UI Modal
39 | {/* The button to open modal */}
40 |
41 | open modal
42 |
43 |
44 | {/* Put this part before