How to Password Protect Individual Pages on Framer
How to Password Protect Individual Pages on Framer
Framer Tips
Aug 29, 2024
How to Password Protect Individual Pages on Framer
Add a new code override and give it a name.
On the left sidebar pane, Go to "Assets" tab
Click (+) to add a new code file
Name the code file
Auth.tsx
Select file type as "New Override"
Click "Create"
Delete the sample code in the new file
Copy and paste the provided code into the
Auth.tsx
file.
import React, { useState, useEffect, useRef } from "react"
import type { ComponentType } from "react"
import { addPropertyControls, ControlType } from "framer"
import {
Eye,
EyeSlash,
LockSimple,
LockSimpleOpen,
ArrowUUpLeft,
} from "phosphor-react"
// ===== CUSTOMIZATION SECTION =====
// Edit this section to customize the password protection
// Add or remove passwords here (case-sensitive)
const ALLOWED_PASSWORDS = ["ABCD", "abcd", "Abcd"]
// Customize the text content here
const TEXT_CONTENT = {
title: "PRIVATE CONTENT",
subtitle: "Enter passcode to continue (passcode: abcd)",
errorMessage: "Incorrect passcode. Please try again.",
buttonText: "Unlock",
returnButtonText: "Return to Projects",
}
// Change this URL to where you want users to return when clicking the link to return
const RETURN_URL = "https://www.google.com"
// Customize the design here
const STYLE_TOKENS = {
colors: {
background: "#FFFFFF", // Background Color
text: "#000000", // Main text color
primary: "#000000", // Color for buttons and important elements
secondaryText: "#605D64", // Color for less important text
buttonText: "#FFFFFF", // Color for the button text
error: "#D8512A", // Color for error messages and error input border
inputBorder: "#CCCCCC", // Default border color for input fields
inputBorderFocus: "#000000", // Border color when input is focused
inputBorderError: "#D8512A", // Border color when there's an error
buttonHover: "#333333", // Button color on hover
linkHover: "#333333", // Home link color on hover
},
fonts: {
heading: "Syncopate, sans-serif", // Font for headings
body: "General Sans, sans-serif", // Font for body text
button: "General Sans, sans-serif", // Font for button text
},
fontSizes: {
title: "clamp(1.25rem, 1.586vw + 1.151rem, 2rem)", // Responsive title size
paragraph: "18px",
input: "16px",
button: "16px",
small: "14px",
},
fontWeights: {
normal: "500",
medium: "600",
bold: "800",
},
spacing: {
xs: "4px",
sm: "8px",
md: "12px",
lg: "24px",
xl: "32px",
xxl: "48px",
},
borderRadius: {
small: "8px",
},
container: {
maxWidth: "400px",
},
}
// ===== END OF CUSTOMIZATION SECTION =====
const styles = {
container: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
backgroundColor: STYLE_TOKENS.colors.background,
fontFamily: STYLE_TOKENS.fonts.body,
color: STYLE_TOKENS.colors.text,
padding: STYLE_TOKENS.spacing.md,
},
header: {
marginBottom: STYLE_TOKENS.spacing.lg,
textAlign: "center",
},
title: {
fontSize: STYLE_TOKENS.fontSizes.title,
fontWeight: STYLE_TOKENS.fontWeights.bold,
fontFamily: STYLE_TOKENS.fonts.heading,
lineHeight: "125%",
marginBottom: STYLE_TOKENS.spacing.xs,
},
subtitle: {
fontFamily: STYLE_TOKENS.fonts.body,
fontWeight: STYLE_TOKENS.fontWeights.normal,
fontSize: STYLE_TOKENS.fontSizes.paragraph,
lineHeight: "150%",
margin: "0px",
color: STYLE_TOKENS.colors.secondaryText,
},
form: {
width: "100%",
maxWidth: STYLE_TOKENS.container.maxWidth,
},
inputContainer: {
position: "relative",
},
input: {
width: "100%",
fontFamily: STYLE_TOKENS.fonts.body,
fontSize: STYLE_TOKENS.fontSizes.paragraph,
fontWeight: STYLE_TOKENS.fontWeights.normal,
paddingTop: STYLE_TOKENS.spacing.md,
paddingBottom: STYLE_TOKENS.spacing.md,
paddingLeft: STYLE_TOKENS.spacing.md,
paddingRight: STYLE_TOKENS.spacing.xxl,
fontSize: STYLE_TOKENS.fontSizes.input,
border: `1px solid ${STYLE_TOKENS.colors.inputBorder}`,
borderRadius: STYLE_TOKENS.borderRadius.small,
outline: "none",
transition: "border-color 0.3s",
WebkitAppearance: "none",
MozAppearance: "none",
appearance: "none",
},
inputError: {
borderColor: STYLE_TOKENS.colors.inputBorderError,
},
showPasswordButton: {
position: "absolute",
right: STYLE_TOKENS.spacing.md,
top: "50%",
transform: "translateY(-50%)",
background: "none",
border: "none",
cursor: "pointer",
padding: "0",
},
button: {
width: "100%",
padding: STYLE_TOKENS.spacing.md,
marginTop: STYLE_TOKENS.spacing.lg,
fontFamily: STYLE_TOKENS.fonts.button,
fontSize: STYLE_TOKENS.fontSizes.button,
fontWeight: STYLE_TOKENS.fontWeights.medium,
color: STYLE_TOKENS.colors.buttonText,
backgroundColor: STYLE_TOKENS.colors.primary,
border: "none",
borderRadius: STYLE_TOKENS.borderRadius.small,
cursor: "pointer",
transition: "background-color 0.3s",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
buttonHovered: {
backgroundColor: STYLE_TOKENS.colors.buttonHover,
},
error: {
color: STYLE_TOKENS.colors.error,
marginTop: STYLE_TOKENS.spacing.md,
fontSize: STYLE_TOKENS.fontSizes.small,
},
returnLink: {
marginTop: STYLE_TOKENS.spacing.xxl,
color: STYLE_TOKENS.colors.text,
textDecoration: "none",
fontSize: STYLE_TOKENS.fontSizes.small,
display: "flex",
alignItems: "center",
},
returnLinkHovered: {
color: STYLE_TOKENS.colors.linkHover,
},
}
export function requireAuth(Component): ComponentType {
return (props) => {
const [authenticated, setAuthenticated] = useState(false)
const [showPassword, setShowPassword] = useState(false)
const [errorMessage, setErrorMessage] = useState("")
const passwordRef = useRef(null)
const [loading, setLoading] = useState(true)
const [hovered, setHovered] = useState(false)
const validateAuth = (e) => {
e.preventDefault() // Prevent default form submission behavior
const inputPassword = e.target.elements.password.value
if (ALLOWED_PASSWORDS.includes(inputPassword)) {
setAuthenticated(true) // Set authenticated state to true
setErrorMessage("") // Clear any existing error message
} else {
setAuthenticated(false) // Ensure authenticated state is false
setErrorMessage(TEXT_CONTENT.errorMessage) // Set error message for invalid password
}
// Optionally clear the password field after submission
e.target.elements.password.value = ""
}
useEffect(() => {
if (passwordRef.current) {
passwordRef.current.focus()
}
setTimeout(() => setLoading(false), 1000)
}, [])
if (!authenticated) {
return (
<div style={styles.container}>
<div style={styles.header}>
<h1 style={styles.title}>{TEXT_CONTENT.title}</h1>
<p style={styles.subtitle}>{TEXT_CONTENT.subtitle}</p>
</div>
<form onSubmit={validateAuth} style={styles.form}>
<div style={styles.inputContainer}>
<input
type={showPassword ? "text" : "password"}
name="password"
ref={passwordRef}
style={{
...styles.input,
...(errorMessage ? styles.inputError : {}),
}}
onFocus={(e) =>
(e.target.style.borderColor =
STYLE_TOKENS.colors.inputBorderFocus)
}
onBlur={(e) =>
(e.target.style.borderColor = errorMessage
? STYLE_TOKENS.colors.inputBorderError
: STYLE_TOKENS.colors.inputBorder)
}
autoComplete="new-password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
style={styles.showPasswordButton}
>
{showPassword ? (
<EyeSlash
size={20}
color={
STYLE_TOKENS.colors.secondaryText
}
/>
) : (
<Eye
size={20}
color={
STYLE_TOKENS.colors.secondaryText
}
/>
)}
</button>
</div>
{errorMessage && (
<p style={styles.error}>{errorMessage}</p>
)}
<button
type="submit"
style={{
...styles.button,
...(hovered ? styles.buttonHovered : {}),
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{hovered ? (
<LockSimpleOpen size={20} />
) : (
<LockSimple size={20} />
)}
<span
style={{
marginLeft: STYLE_TOKENS.spacing.sm,
}}
>
{TEXT_CONTENT.buttonText}
</span>
</button>
</form>
<a
href={RETURN_URL}
style={styles.returnLink}
onMouseEnter={(e) =>
(e.currentTarget.style.color =
STYLE_TOKENS.colors.linkHover)
}
onMouseOut={(e) =>
(e.currentTarget.style.color =
STYLE_TOKENS.colors.text)
}
>
<ArrowUUpLeft
size={16}
style={{ marginRight: STYLE_TOKENS.spacing.sm }}
/>
{TEXT_CONTENT.returnButtonText}
</a>
</div>
)
}
return <Component {...props} />
}
}
// Add property controls (if needed)
addPropertyControls(requireAuth, {
// Add your property controls here
})
Personalize the design and copy
Change the values of the design tokens: ALLOWED_PASSWORDS
, TEXT_CONTENT
, RETURN_URL
, STYLE_TOKENS
to fit your brand.
Save code override file
Press CMD
+ S
on Mac or CTRL
+ S
on Windows to save
Extra
You can set multiple passwords that will work across all pages using this code override. Simply edit the ALLOWED_PASSWORDS
list. For example:
const ALLOWED_PASSWORDS = ["myPassword123", "letMeIn", "secretCode"]
Note: If you want different passwords for separate pages, you'll need to duplicate the "Auth.tsx" file, change the passwords in each copy, and apply the appropriate code override to each page.
Ready to Begin our journey?
Ready to Begin our journey?
Contact us today to discuss your project and start your journey toward a stronger online presence. Get a no-pressure free quote!
Contact us today to discuss your project and start your journey toward a stronger online presence. Get a no-pressure free quote!