Skip to content

Commit d657619

Browse files
ahuseynCopilot
andauthored
[next]: Add Authentication POC (#2173)
* init preview example * update gql client, add headers param * update template-hierarchy, add id and asPreview * add preview utility to @faustjs/nextjs package * initial * new templates * add homepage * style fixes * update uri expression * rename example name, add readme * init project * update templates, add data fetching * remove logs, add comments, minor fixes * add kitchen-sink example * add seedNode to templateHierarchy * add preview * add preview to sveltekit templateHierarchy * add auth package and example usage * add alternative preview with auth package, add login page, client updates * remove token handler * Update examples/nextjs/kitchen-sink/src/components/Header.js Co-authored-by: Copilot <[email protected]> * todo updates * Update packages/nextjs/package.json Co-authored-by: Copilot <[email protected]> * init: toolbar integration (#2202) --------- Co-authored-by: Copilot <[email protected]>
1 parent 9287c79 commit d657619

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2599
-131
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ plugins/faustwp/.docker/plugins/index.php
1818

1919
# Ignore the WordPress source where used by various development environments
2020
wordpress/
21+
node_modules/

examples/nextjs/kitchen-sink/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12-
"@faustjs/nextjs": "workspace:*",
13-
"@faustjs/template-hierarchy": "workspace:*",
12+
"@faustjs/auth": "workspace:*",
1413
"@faustjs/data-fetching": "workspace:*",
14+
"@faustjs/nextjs": "workspace:*",
1515
"graphql": "^16.11.0",
1616
"graphql-tag": "^2.12.6",
17+
"iron-session": "^8.0.4",
1718
"next": "15.2.4",
1819
"react": "^19.0.0",
19-
"react-dom": "^19.0.0"
20+
"react-dom": "^19.0.0",
21+
"@wpengine/faust-next-toolbar": "file:/../../../packages/faust-next-toolbar",
22+
"@wpengine/hwp-toolbar": "github:wpengine/hwptoolkit#toolbar"
2023
},
2124
"devDependencies": {
2225
"@eslint/eslintrc": "^3",
Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
1-
import Link from "next/link";
1+
import Link from 'next/link';
22

33
export function BlogPostItem({ post }) {
4-
const { title, date, excerpt, uri, featuredImage } = post ?? {};
4+
const { title, date, excerpt, uri, featuredImage } = post ?? {};
55

6-
return (
7-
<article className='container max-w-4xl px-10 py-6 mx-auto rounded-lg shadow-sm bg-gray-50 mb-4'>
8-
<time dateTime={date} className='text-sm text-gray-600'>
9-
{new Date(date).toLocaleDateString("en-US", {
10-
year: "numeric",
11-
month: "long",
12-
})}
13-
</time>
6+
return (
7+
<article className="container max-w-4xl px-10 py-6 mx-auto rounded-lg shadow-sm bg-gray-50 mb-4">
8+
<time
9+
dateTime={date}
10+
className="text-sm text-gray-600"
11+
suppressHydrationWarning>
12+
{new Date(date).toLocaleDateString('en-US', {
13+
year: 'numeric',
14+
month: 'long',
15+
})}
16+
</time>
1417

15-
{featuredImage && (
16-
<img src={featuredImage?.node?.sourceUrl} alt='' className='w-full h-48 object-cover rounded-t-lg mt-2 mb-4' />
17-
)}
18+
{featuredImage && (
19+
<img
20+
src={featuredImage?.node?.sourceUrl}
21+
alt=""
22+
className="w-full h-48 object-cover rounded-t-lg mt-2 mb-4"
23+
/>
24+
)}
1825

19-
<h2 className='mt-3'>
20-
<Link href={uri} className='text-2xl font-bold hover:underline'>
21-
{title}
22-
</Link>
23-
</h2>
26+
<h2 className="mt-3">
27+
<Link href={uri} className="text-2xl font-bold hover:underline">
28+
{title}
29+
</Link>
30+
</h2>
2431

25-
<div className='mt-2 mb-4' dangerouslySetInnerHTML={{ __html: excerpt }} />
32+
<div
33+
className="mt-2 mb-4"
34+
dangerouslySetInnerHTML={{ __html: excerpt }}
35+
/>
2636

27-
<Link href={uri} className='hover:underline text-orange-600 mt-4'>
28-
Read more
29-
</Link>
30-
</article>
31-
);
37+
<Link href={uri} className="hover:underline text-orange-600 mt-4">
38+
Read more
39+
</Link>
40+
</article>
41+
);
3242
}
Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,100 @@
1-
/* eslint-disable @next/next/no-html-link-for-pages */
21
import Link from 'next/link';
2+
import { useState } from 'react';
3+
import Login from './Login';
4+
import { useLogout, useUser } from '@faustjs/nextjs/pages';
5+
import { useRouter } from 'next/router';
36

47
export default function Header() {
8+
const { user, refetch, isAuthenticated } = useUser();
9+
const { logout } = useLogout();
10+
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
11+
const route = useRouter();
12+
13+
const openLoginModal = () => setIsLoginModalOpen(true);
14+
const closeLoginModal = () => setIsLoginModalOpen(false);
15+
16+
const onLoggedIn = () => {
17+
closeLoginModal();
18+
refetch();
19+
};
20+
21+
const logoutUser = () => {
22+
logout({
23+
onSuccess: () => {
24+
refetch();
25+
},
26+
});
27+
};
28+
529
return (
6-
<header className="bg-gray-800 text-white py-4 px-8">
7-
<div className="flex justify-between items-center max-w-4xl mx-auto">
8-
<div className="text-3xl font-semibold">
9-
<Link href="/">Headless</Link>
30+
<>
31+
<header className="bg-violet-900 text-white py-4 px-8">
32+
<div className="flex justify-between items-center max-w-4xl mx-auto">
33+
<div className="text-3xl font-semibold">
34+
<Link href="/">Headless</Link>
35+
</div>
36+
37+
<nav className="flex items-center gap-4">
38+
<Link href="/" className="text-lg hover:underline">
39+
Home
40+
</Link>
41+
42+
{isAuthenticated ? (
43+
<div className="relative group">
44+
<button className="text-lg hover:bg-gray-700 px-3 py-2 rounded-md transition-colors duration-200 flex items-center space-x-1">
45+
<span>
46+
Welcome,{' '}
47+
<strong>{user?.name || user?.username || 'User'}</strong>
48+
</span>
49+
<svg
50+
className="w-4 h-4"
51+
fill="none"
52+
stroke="currentColor"
53+
viewBox="0 0 24 24">
54+
<path
55+
strokeLinecap="round"
56+
strokeLinejoin="round"
57+
strokeWidth={2}
58+
d="M19 9l-7 7-7-7"
59+
/>
60+
</svg>
61+
</button>
62+
63+
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50">
64+
<div className="py-1">
65+
<button
66+
onClick={logoutUser}
67+
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors duration-200">
68+
Logout
69+
</button>
70+
</div>
71+
</div>
72+
</div>
73+
) : route.asPath !== '/login' ? (
74+
<button
75+
onClick={openLoginModal}
76+
className="text-lg hover:underline cursor-pointer bg-transparent border-none text-white">
77+
Login
78+
</button>
79+
) : null}
80+
</nav>
1081
</div>
82+
</header>
1183

12-
<nav className="space-x-6">
13-
<Link href="/" className="text-lg hover:underline">
14-
Home
15-
</Link>
16-
</nav>
17-
</div>
18-
</header>
84+
{isLoginModalOpen && (
85+
<div className="fixed inset-0 bg-gray-900/30 flex items-center justify-center z-50">
86+
<div className="bg-white rounded-lg p-8 max-w-md w-full mx-4 relative">
87+
<button
88+
onClick={closeLoginModal}
89+
className="absolute top-4 right-4 text-gray-500 hover:text-gray-700 text-xl font-bold">
90+
×
91+
</button>
92+
<h2 className="text-2xl font-bold mb-6 text-gray-800">Login</h2>
93+
94+
<Login closeModal={onLoggedIn} />
95+
</div>
96+
</div>
97+
)}
98+
</>
1999
);
20100
}

examples/nextjs/kitchen-sink/src/components/Layout.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import { useRouter } from 'next/router';
21
import Header from './Header';
3-
import PreviewButton from './PreviewButton';
42

53
export default function Layout({ children }) {
6-
const router = useRouter();
4+
;
75

86
return (
97
<>
108
<Header />
119
<main className="bg-stone-100 text-gray-800 pb-16 pt-8 min-h-screen">
1210
{children}
13-
14-
{router.isPreview && <PreviewButton />}
1511
</main>
1612
</>
1713
);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useLogin } from '@faustjs/nextjs/pages';
2+
import { useState } from 'react';
3+
4+
export default function Login({ closeModal = () => {}, onSuccess = () => {} }) {
5+
const [usernameEmail, setUsernameEmail] = useState('');
6+
const [password, setPassword] = useState('');
7+
8+
const { login, isLoading, error } = useLogin();
9+
10+
const submitForm = (e) => {
11+
e.preventDefault();
12+
13+
login({
14+
input: {
15+
credentials: {
16+
password,
17+
username: usernameEmail,
18+
},
19+
},
20+
onSuccess: () => {
21+
onSuccess();
22+
closeModal();
23+
},
24+
});
25+
};
26+
27+
return (
28+
<form onSubmit={submitForm} className="space-y-4">
29+
<div>
30+
<input
31+
type="text"
32+
name="username"
33+
placeholder="Username or Email"
34+
value={usernameEmail}
35+
onChange={(e) => setUsernameEmail(e.target.value)}
36+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
37+
required
38+
/>
39+
</div>
40+
<div>
41+
<input
42+
type="password"
43+
name="password"
44+
placeholder="Password"
45+
value={password}
46+
onChange={(e) => setPassword(e.target.value)}
47+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
48+
required
49+
/>
50+
</div>
51+
{error && (
52+
<div className="text-red-600 text-sm bg-red-50 border border-red-200 rounded-md p-2">
53+
{error.message || 'Login failed. Please try again.'}
54+
</div>
55+
)}
56+
<button
57+
type="submit"
58+
disabled={isLoading}
59+
className="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 disabled:cursor-not-allowed text-white font-medium py-2 px-4 rounded-md transition-colors duration-200">
60+
{isLoading ? 'Signing in...' : 'Sign In'}
61+
</button>
62+
</form>
63+
);
64+
}

examples/nextjs/kitchen-sink/src/components/PreviewButton.js

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defaultIronOptions } from '@faustjs/auth';
2+
3+
export const sessionConfig = {
4+
...defaultIronOptions,
5+
password: process.env.SESSION_PASSWORD,
6+
cookieName: 'faust-auth',
7+
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import availableTemplates from '@/wp-templates';
2+
import availableQueries from '@/wp-templates/templateQueries';
3+
import { fetchTemplateQueries } from '@faustjs/data-fetching';
4+
import { uriToTemplate } from '@faustjs/nextjs/pages';
5+
6+
// Resolves the WordPress route based on the identifier (slug or ID) and fetches the necessary data
7+
// for the corresponding template. It returns the props needed for rendering the page.
8+
// If the route or template is not found, it returns a 404 response.
9+
10+
export async function resolveWpRoute(identifier, isPreview, client) {
11+
const uri = identifier ? `/${identifier.join('/')}/` : '/';
12+
13+
const variables = isPreview
14+
? {
15+
id: identifier?.[0],
16+
asPreview: true,
17+
}
18+
: { uri };
19+
20+
try {
21+
const templateData = await uriToTemplate({
22+
...variables,
23+
availableTemplates: Object.keys(availableTemplates),
24+
wordpressUrl: process.env.NEXT_PUBLIC_WORDPRESS_URL,
25+
});
26+
27+
if (
28+
!templateData?.template?.id ||
29+
templateData?.template?.id === '404 Not Found'
30+
) {
31+
return { notFound: true };
32+
}
33+
34+
const queriesData = await fetchTemplateQueries({
35+
availableQueries,
36+
templateData,
37+
client,
38+
locale: templateData?.seedNode?.locale,
39+
});
40+
41+
return {
42+
props: {
43+
uri,
44+
templateData: JSON.parse(JSON.stringify(templateData)),
45+
queriesData,
46+
},
47+
};
48+
} catch (error) {
49+
console.error('Error resolving template:', error);
50+
return { notFound: true };
51+
}
52+
}

0 commit comments

Comments
 (0)