How to create Custom Pages?

@hammad, I think you should use gutenberg blocks. Adding more styles in the content has usually been an anti pattern in WordPress, I think.


By the way, the other day I realized that there is another way to add a custom, non-WordPress page to Frontity.

Instead of creating a custom handler, like this:

const signUpHandler = {
  pattern: "/sign-up/",
  func: ({ state }) => {
    state.source.data["/sign-up/"].isSignUp = true;
  }
}

You can add the data to the state:

const myPackage = {
  state: {
    source: {
      data: {
        "/sign-up/": {
          isReady: true,
          isFetching: false,
          isSignUp: true
        }
      }
    }
  }
}

actions.source.fetch("/sign-up") wonā€™t do the fetch because isReady is true.

The only case where it wouldnā€™t work is if someone does actions.source.fetch("/sign-up", { force: true }) but that should never happen with a custom page like this.

3 Likes

Can someone help me with adding a custom page to the frontity project.

I added the following ā€¦

const signUpHandler = {

  pattern: "/signup/",

  func: ({ route, state }) => {

    Object.assign(state.source.data[route], {

      type: "page",

      isSignUp: true,

    });

  },

};

ā€¦

actions: {

    theme: {

      actions: {

        init: ({ libraries }) => {

          libraries.source.handlers.push(signUpHandler);

        },

      },

      beforeSSR: before,

      beforeCSR: before,

      toggleMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;

      },

      closeMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = false;

      },

    },

  },

ā€¦
frontity.settings

 ["Sign up", "/signup/"],

ā€¦

signup.js

import React, { useEffect } from "react";

//import { connect, styled } from "frontity";

const Signup = () => {

  return (

    <div>

      <h2>Sign up Page</h2>

    </div>

  );

};

export default Signup;

ā€¦

theme index.js

<Switch>

          <Signup when={data.isSignUp} />

          <Loading when={data.isFetching} />

          <List when={data.isArchive} />

          <Post when={data.isPostType} />

          <PageError when={data.isError} />

ā€¦

I keep getting 404 error. and react_devtools_backend.js:2273 ServerError: post type from endpoints ā€œposts,pages,mediaā€ with slug ā€œsignupā€ not found

Any guidance is much appreciated.

I think you forgot to add a priority to the handler. For example priority: 10.

By the way, instead of a handler, you can try adding that data to the state:

const myPackage = {
  state: {
    source: {
      data: {
        "/signup/": {
          isReady: true,
          isFetching: false,
          isSignUp: true,
        },
      },
    },
  },
};

Let me know if that works :slightly_smiling_face:

3 Likes

Yes adding the priority:10 to the handler fixed it!!! And also I added the init wrong, if you see code above, I inserterted the init in actions.theme.actions.init whilst it should have been actions.theme.initā€¦ Thank you so much!!!

2 Likes

Hi ! I created a new route to custom component when I will call an external API using your code. I can get a listing without any problem. Now I want to fetch to a specific id to fetch to a new component

data: {
        "/hello/:id": {
          isReady: true,
          isFetching: false,
          isHello: true,
        },
      },

However it wonā€™t workā€¦ any suggestion ?

If you want to use a pattern you need to create a handler:

const helloHandler = {
  pattern: "/hello/:id",
  func: ({ state, link, params }) => {
    state.source.data[link].isReady = true;
    state.source.data[link].isHello = true;
    state.source.data[link].id = params.id;
  },
};

And then add it to libraries.source.handlers.

Hello @luisherranz, I tried your solution but when I am typing any id I get a 404 page :confused:

  const helloHandler = {
  pattern: "/hello/:id",
  func: ({ state, link, params }) => {
    state.source.data[link].isHello = true;
    state.source.data[link].id = params.id;
  },
};

And

packages: [
{
  name: "@awsmin/f1",
  state: {
    theme: {
      menu: [
        ["Nos occasions", "/"],
        ["L'atelier", "/category/nature/"],
        ["Nos services", "/category/travel/"],
        ["Qui sommes-nous", "/tag/japan/"],
        ["ActualitƩs", "/about-us/"],
        ["Nous contacter", "/about-us/"],
      ],
      featured: {
        showOnList: false,
        showOnPost: false,
      },
    },
  },
  actions: {
    theme: {
      init: ({ libraries }) => {
        // Use html2react to process the <img> tags inside the content HTML.
        // libraries.html2react.processors.push(image);

        // Add the handler to wp-source.
        libraries.source.handlers.push(helloHandler);
      },
    },
  },

and my index.js I create a component with sample h1 at that time

<Switch>
      <Loading when={data.isFetching} />
      <List when={data.isArchive} />
      <HomeScreen when={data.isHome} />
      <VehicleScreen when={data.isOffers} />
      <Entretien when={data.isEntretien} />
      <Hello when={data.isHello} />
      <Jobs when={data.isAwsmJobOpenings} />
      <Page when={data.isPage} />
      <Post when={data.isPostType} />
      <PageError when={data.isError} />
    </Switch>
1 Like

Hi @floriandupuis1996,

Can you please provide a repo or code-sandbox with your code? This is especially helpful to find solutions of technical issues with specific code

I use it, can fix this problem !

const profilePage = {
	name: 'member',
	priority: 1,
	pattern: '/member/:user',
	func: ({ state, link, params }) => {
		state.source.data[link].isProfile = true
		state.source.data[link].user = params.user
	},
}
]
	actions: {
		actions: {
			init: ({ libraries }) => {
				libraries.source.handlers.push(profilePage)
			},
		},
		theme: {
...
}

Yeah I succeed

Hey @floriandupuis1996 itā€™d be great if you can share with the rest how you finally solved and what was the issue you were having :smiley:

Ths thing is I had handler in frontity.settings.js instead of indexā€¦ now I am looking to get the param.id display on my component but this is not easy taskā€¦

any idea ?

Hi there, Iā€™m Pilar. Iā€™m starting with Frontity and Iā€™ve struggling with custom handlers, custom pages and custom queries, browsing this thread and many others, reading the docs, etc. Iā€™ve finally made it work so I wanted to share the code in case itā€™s helpful for anyone, as yours has been to me :slight_smile:

The base of my project is a large set of Galician traditional sayings or ā€œrefranesā€. The main user interaction will be the search (and also copying the text of the quote, sharing it as an image, etc. but thatā€™s another story). For SEO reasons I want some popular searches to be on the menu and have their own url, like ā€œsayings-about-winterā€ (instead of ā€œdomain.com/?s=winterā€), without having to categorize them. So I created a custom handler that takes the last word of that url pattern, whatever it is, (in this case, ā€œwinterā€) and shows all the posts that contain that specific word.

Thatā€™s how I achieved it, I donā€™t know if itā€™s a better way to to do it? It would be simpler with the future method described here, but for now itā€™s working :slight_smile:!

In mars-theme/src/index.js:

import Theme from "./components";
import image from "@frontity/html2react/processors/image";
import iframe from "@frontity/html2react/processors/iframe";

const searchUrls = {
  priority: 1,
  pattern: "/sayings-about-:search",
  func: async ({ link, params, state, libraries, force }) => {
    
    const response = await libraries.source.api.get({
      endpoint: "posts",
      params: { search: params.search }
    });

    const items = await libraries.source.populate({
      response,
      state,
      force,
    });    

    Object.assign(state.source.data[link], {
      isSearchUrl: true,
      //add queried posts to data
      items,
    });

  }
}
const marsTheme = {
  name: "@frontity/mars-theme",
  roots: {
    theme: Theme,
  },
  state: {
    theme: {

      },
    },
  },
  actions: {
    theme: {   
      init: ({ libraries }) => {
        libraries.source.handlers.push(searchUrls);
      },    
    },
  },
};

export default marsTheme;

In src/components/index.js:

<Switch>
  <Loading when={data.isFetching} />
  <List when={data.isArchive} />
  <Post when={data.isPostType} />
  <ListSearch when={data.isSearchUrl} />
</Switch>

In components/list/list-search.js:

const ListSearch = ({ state, libraries, link }) => {
    const data = state.source.get(state.router.link);
    return (
      <Container>
        {data.items.map(({ type, id }) => {
          const item = state.source[type][id];
          // Render one Item component for each one.
          return <Item key={item.id} item={item} />;
        })}
        <Pagination />
      </Container>
    );
};

Now if I go to /sayings-about-winter I see all the posts that contain the word ā€œwinterā€, and /sayings-about-spring shows all the posts containing ā€œspringā€, etc.

And I can add these links to the menu in frontity.settings.js (or anywhere else):

      "menu": [
        [
          "Home",
          "/"
        ],
        [
          "Sayings about winter",
          "/sayings-about-winter/"
        ],
        [
          "Sayings about spring",
          "/sayings-about-spring/"
        ],
      ],
5 Likes

Hi @decrecementofeliz

Welcome to the community. It looks like youā€™re doing great on your journey with Frontity! :tada:

Thanks for presenting this solution. Iā€™m sure other Frontity users will find it useful.

Weā€™d love to know more about what youā€™re building with Frontity. Weā€™re always looking for projects to feature in our showcase page.

2 Likes

Hi, here is my frontity page: http://api-tech.pl/ but I have problem with routes,

I canā€™t get to the site directly from the link for example http://api-tech.pl/kontakt give me 404;
also when Iā€™m for example first render FOTOGRAFIA > PODLASIE (PHOTOGRAPHY > PODLASIE)
looks good, and after refresh my gallery disappear.
I thought something wrong in Theme index.js and with handlers

 <Main>

 {data.isPodlasiePage && <PodlasiePage />} 
      {data.isFrontPage && <Home />}  
      {data.isGoryPage && <GoryPage />} 
      {data.isTimelapsePage && <TimelapsePage />}
      {data.isCommercialPage && <ComercialMoviesPage />}
      {data.isGoPodlasiePage && <GoPodlasiePage />}
      {data.isColaboPage && <CollaborationPage />}
      {data.isContactPage && <ContactPage />} 
      {data.isPolitykaPage && <PrivacyPolicyPage />}

<Switch>
  <Loading when={data.isFetching} />
  <List when={data.isArchive} />
  <PageError when={data.isError} />
</Switch>

and here:

const homeHandler = {

  pattern: "/",

  func: ({ state }) => {

    state.source.data["/"].isFrontPage = true;

    state.source.data["/"].isArchive = false;

  }

}

const fotografiaHandler = {

  pattern: "/fotografia/",

  func: ({ state }) => {

    state.source.data["/astrofoto/"].isShovvPage = true;

    state.source.data["/astrofoto/"].isArchive = false;

  }

}

const podlasieHandler = {

  pattern: "/podlasie/",

  func: ({ state }) => {

    state.source.data["/podlasie/"].isPodlasiePage = true;

    state.source.data["/podlasie/"].isArchive = false;

  }

}

// const astroHandler = {

//   pattern: "/astrofoto/",

//   func: ({ state }) => {

//     state.source.data["/astrofoto/"].isAstroPage = true;

//     state.source.data["/astrofoto/"].isArchive = false;

//   }

// }

const goryHandler = {

  pattern: "/gory/",

  func: ({ state }) => {

    state.source.data["/gory/"].isGoryPage = true;

    state.source.data["/gory/"].isArchive = false;

    // state.source.data["/gory/"].isPostType = false;

  }

}

const wadirumHandler = {

  pattern: "/wadirum/",

  func: ({ state }) => {

    state.source.data["/wadirum/"].isWadirumPage = true;

    state.source.data["/wadirum/"].isArchive = false;

    console.log('inside handler')

  }

}

const colaboHandler = {

  pattern: "/wspolpraca/",

  func: ({ state }) => {

    state.source.data["/wspolpraca/"].isColaboPage = true;

    state.source.data["/wspolpraca/"].isArchive = false;

  }

}

const timelapseHandler = {

  pattern: "/timelapse/",

  func: ({ state }) => {

    state.source.data["/timelapse/"].isTimelapsePage = true;

    state.source.data["/timelapse/"].isArchive = false;

  }

}

const commercialHandler = {

  pattern: "/komercyjne/",

  func: ({ state }) => {

    state.source.data["/komercyjne/"].isCommercialPage = true;

    state.source.data["/komercyjne/"].isArchive = false;

  }

}

const goPodlasieHandler = {

  pattern: "/go-podlasie/",

  func: ({ state }) => {

    state.source.data["/go-podlasie/"].isGoPodlasiePage = true;

    state.source.data["/go-podlasie/"].isArchive = false;

  }

}

const contactHandler = {

  pattern: "/kontakt/",

  func: ({ state }) => {

    state.source.data["/kontakt/"].isContactPage = true;

  }

}

const privacyPolicyHandler = {

  pattern: "/polityka-prywatnosci/",

  func: ({ state }) => {

    state.source.data["/polityka-prywatnosci/"].isPolitykaPage = true;

    state.source.data["/polityka-prywatnosci/"].isArchive = false;

  }

}

const marsTheme = {

  name: "@frontity/mars-theme",

  roots: {

    /**

     * In Frontity, any package can add React components to the site.

     * We use roots for that, scoped to the `theme` namespace.

     */

    theme: Theme,

  },

  state: {

    /**

     * State is where the packages store their default settings and other

     * relevant state. It is scoped to the `theme` namespace.

     */

    theme: {

      autoPrefetch: "in-view",

      menu: [],

      isMobileMenuOpen: false,

      featured: {

        showOnList: false,

        showOnPost: false,

      },

    },

  },

  /**

   * Actions are functions that modify the state or deal with other parts of

   * Frontity like libraries.

   */

  actions: {

    theme: {

      toggleMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;

      },

      closeMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = false;

      },

      init: ({ libraries }) => {

        // Use html2react to process the <img> tags inside the content HTML.

        libraries.html2react.processors.push(image);

        // Add the handler to wp-source.

        libraries.source.handlers.push(homeHandler);

        libraries.source.handlers.push(fotografiaHandler);

        libraries.source.handlers.push(podlasieHandler);

        // libraries.source.handlers.push(astroHandler);

        libraries.source.handlers.push(goryHandler);

        libraries.source.handlers.push(wadirumHandler);

        libraries.source.handlers.push(colaboHandler);

        libraries.source.handlers.push(timelapseHandler);

        libraries.source.handlers.push(commercialHandler);

        libraries.source.handlers.push(goPodlasieHandler);

        libraries.source.handlers.push(contactHandler);

        libraries.source.handlers.push(privacyPolicyHandler);

        console.log('init')

      },

      

      beforeSSR: async ({actions}) => {

         await actions.source.fetch("/contact");

         await actions.source.fetch("/gora");

        }

      },

    },

also in my index.js I used the approach below with same effect.

 <ContactPage when={data.isContactPage}/>
  <Home when={data.isFrontPage}/>
  <PodlasiePage when={data.isPodlasiePage}/>

Can anybody tell me how to get it right?

Looks like itā€™s working now, maybe you fixed it already

Hi @rborowski.shovv,

As @furrysmile2 says, your categories links seem to be working fine from both Server side and Client Side navigation

Is it working now for you?

Yes, itā€™s working well. Last thing is that after refreshing page for exmaple http://api-tech.pl/kontakt in Firefox, losing styles of material ui icons, and get huge

3 posts were split to a new topic: 404 page since updating to latest Frontity

A post was split to a new topic: Custom path is leading to the 404 page