Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Feat custom domain #9436

Merged
merged 37 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d2853d8
feat: use custom domain
eddiejaoude Oct 9, 2023
7a69b23
feat: custom domain
eddiejaoude Oct 9, 2023
d55da15
fix: profile model domain property
eddiejaoude Oct 10, 2023
b2e9f81
Merge branch 'main' into feat-custom-domain
eddiejaoude Oct 10, 2023
2946452
feat: allow user to set custom domain
eddiejaoude Oct 10, 2023
7d0c22f
fix: efficiency improvements to middleware
eddiejaoude Oct 11, 2023
9259ef7
fix: domain not used
eddiejaoude Oct 11, 2023
c37edc6
fix: default value for domain
eddiejaoude Oct 11, 2023
cf5f4c1
fix: deploy custom domain to preview
eddiejaoude Oct 12, 2023
9e6061f
fix: hardcoded middleware domain
eddiejaoude Oct 12, 2023
23514b4
fix: debug code for middleware
eddiejaoude Oct 12, 2023
35d3d2e
fix: hostname and domain in middleware
eddiejaoude Oct 12, 2023
593b165
fix: hostname replace all
eddiejaoude Oct 12, 2023
0cc8e96
fix: domain api search
eddiejaoude Oct 13, 2023
1614504
fix: debugging domain api search
eddiejaoude Oct 13, 2023
7d9a1e3
fix: manage premium page form
eddiejaoude Oct 13, 2023
fc6d715
wip: add custom domain to vercel
eddiejaoude Oct 14, 2023
7533de5
merged 'main' into 'feat-custom-domain'
eddiejaoude Oct 24, 2023
51f3448
Merge branch 'main' into feat-custom-domain
eddiejaoude Oct 25, 2023
71e38e6
feat: premium domain add to vercel
eddiejaoude Oct 25, 2023
4dfbe11
feat: custom domain to team + project
eddiejaoude Oct 26, 2023
e773195
Merge branch 'main' into feat-custom-domain
eddiejaoude Nov 3, 2023
e2026bc
fix: remove custom url protocols
eddiejaoude Nov 4, 2023
05fdaff
fix: extra custom domain check
eddiejaoude Nov 4, 2023
24954a7
fix: custom domain ignore www
eddiejaoude Nov 4, 2023
2ac2ce8
feat: hide nav+footer if custom domain
eddiejaoude Nov 4, 2023
c38cfa5
fix: custom domain vercel error handling
eddiejaoude Nov 5, 2023
0095774
docs: improve premium docs for dns
eddiejaoude Nov 5, 2023
cb5a6f9
fix: extra debugging for custom domain
eddiejaoude Nov 6, 2023
2211e1c
fix: more debugging for custom domain
eddiejaoude Nov 6, 2023
464c84b
fix: even more debugging for custom domain
eddiejaoude Nov 6, 2023
9ed22cf
fix: middleware rewrite condition
eddiejaoude Nov 6, 2023
f1ea98b
fix: www in custom base domain
eddiejaoude Nov 6, 2023
a60cd72
docs: custom domain setup
eddiejaoude Nov 6, 2023
18c10bc
feat: custom domain vercel status
eddiejaoude Nov 6, 2023
5b641dd
fix: logging when there are errors
eddiejaoude Nov 6, 2023
3c93048
docs: changelog feeature
eddiejaoude Nov 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ STRIPE_SECRET_KEY=""
STRIPE_PREMIUM_PRICING_ID=""
STRIPE_WEBHOOK_SECRET=""
NEXT_PUBLIC_PREMIUM_SUPPORT_URL=""

VERCEL_PROJECT_ID=""
VERCEL_TEAM_ID=""
VERCEL_AUTH_TOKEN=""
2 changes: 1 addition & 1 deletion .github/workflows/vercel-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_dispatch:
push:
branches:
- feat-middleware
- feat-custom-domain

jobs:
deploy:
Expand Down
21 changes: 11 additions & 10 deletions components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import Link from "./Link";

export default function Button({
primary = false,
disable,
disabled = false,
className,
overrideClassNames = false,
children,
...restProps
}) {
let defaultClassName =
"w-full inline-flex items-center flex-1 justify-center rounded-md border-2 border-primary-high dark:border-white hover:border-transparent px-5 py-3 text-base font-medium first-letter:bg-white transition duration-400 ease-in-out";
!disable
? (defaultClassName += primary
let defaultClassName = classNames(
"w-full inline-flex items-center flex-1 justify-center rounded-md border-2 border-primary-high dark:border-white hover:border-transparent px-5 py-3 text-base font-medium first-letter:bg-white transition duration-400 ease-in-out",
!disabled
? primary
? " text-primary-medium bg-secondary-medium hover:bg-tertiary-medium"
: " text-secondary-high dark:text-secondary-high-high hover:text-white dark:hover:text-white dark:bg-primary-low hover:bg-secondary-medium dark:hover:bg-secondary-medium")
: (defaultClassName += disable
? " border-2 border-red border shadow-sm bg-primary-low text-primary-medium cursor-not-allowed "
: " cursor-pointer");
: " text-secondary-high dark:text-secondary-high-high hover:text-white dark:hover:text-white dark:bg-primary-low hover:bg-secondary-medium dark:hover:bg-secondary-medium"
: disabled
? " border-2 border-red border shadow-sm bg-primary-low text-primary-medium cursor-not-allowed "
: " cursor-pointer",
);

const link = (
<Link
Expand All @@ -36,7 +37,7 @@ export default function Button({
className={
overrideClassNames ? className : classNames(defaultClassName, className)
}
disabled={disable}
disabled={disabled}
{...restProps}
>
{children}
Expand Down
1 change: 1 addition & 0 deletions components/form/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const Input = forwardRef(
id={name}
name={name}
value={value}
disabled={disabled}
onKeyDown={handleKeydown}
{...restProps}
/>
Expand Down
38 changes: 37 additions & 1 deletion middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { NextResponse } from "next/server";

export const config = {
matcher: [
"/",

// account management
"/account/:path*",
"/api/account/:path*",
Expand All @@ -14,10 +16,44 @@ export const config = {
};

export async function middleware(req) {
const hostname = req.headers.get("host");
const reqPathName = req.nextUrl.pathname;
const sessionRequired = ["/account", "/api/account"];
const adminRequired = ["/admin", "/api/admin"];
const adminUsers = process.env.ADMIN_USERS.split(",");
const reqPathName = req.nextUrl.pathname;

// check if custom domain
if (reqPathName === "/") {
let res;
let profile;
let url = `${
process.env.NEXT_PUBLIC_BASE_URL
}/api/search/${encodeURIComponent(hostname)}`;
try {
res = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
profile = await res.json();
} catch (e) {
console.error(url, e);
return NextResponse.error(e);
}

if (
profile.username &&
profile.user.type === "premium" &&
profile.settings?.domain &&
hostname === profile.settings.domain
) {
// if match found rewrite to custom domain and display profile page
return NextResponse.rewrite(
new URL(`/${profile.username}`, `http://${profile.settings.domain}`),
);
}
}

// if not in sessionRequired or adminRequired, skip
if (
Expand Down
14 changes: 12 additions & 2 deletions models/Profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,20 @@ const ProfileSchema = new Schema(
type: Boolean,
default: false,
},
domain: {
type: String,
default: "",
get: (v) => v.replaceAll("|", "."),
set: (v) => v.replaceAll(".", "|"),
validator: function (v) {
return /^((?!-)[A-Za-z0-9-]{1, 63}(?<!-)\\.)+[A-Za-z]{2, 6}$/.test(v);
},
message: (props) => `${props.value} is not a valid domain!`,
},
},
},
{ timestamps: true },
{ timestamps: true, toJSON: { getters: true } },
);

module.exports =
mongoose.models.Profile || mongoose.model("Profile", ProfileSchema);
mongoose.models?.Profile || mongoose.model("Profile", ProfileSchema);
2 changes: 1 addition & 1 deletion pages/account/manage/links.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default function ManageLinks({ BASE_URL, username, links }) {
{!reorder && (
<Button
onClick={() => setReorder(true)}
disable={linkList.length < 2}
disabled={linkList.length < 2}
>
<ArrowPathIcon className="h-5 w-5 mr-2" />
REORDER
Expand Down
120 changes: 85 additions & 35 deletions pages/account/manage/premium.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { getSettingsApi } from "pages/api/account/manage/settings";
import Toggle from "@components/form/Toggle";
import Notification from "@components/Notification";
import Link from "@components/Link";
import Input from "@components/form/Input";
import Button from "@components/Button";

export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions);
Expand Down Expand Up @@ -45,28 +47,47 @@ export default function ManageSettings({
}) {
const router = useRouter();
const { success } = router.query;
const [showNotification, setShowNotification] = useState(false);
const [showNotification, setShowNotification] = useState({
show: false,
type: "",
message: "",
additionalMessage: "",
});
const [hideNavbar, setHideNavbar] = useState(settings.hideNavbar || false);
const [hideFooter, setHideFooter] = useState(settings.hideFooter || false);
const [domain, setDomain] = useState(
settings.domain?.replaceAll("|", ".") || "",
); // TODO: use getter/setter instead
const [enableForm] = useState(accountType === "premium" ? true : false);

const toggle = async (setting) => {
if (accountType !== "premium") {
return;
}
const handleSubmit = async (e) => {
e.preventDefault();
const res = await fetch(`${BASE_URL}/api/account/manage/settings`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
hideNavbar: setting === "hideNavbar" ? !hideNavbar : hideNavbar,
hideFooter: setting === "hideFooter" ? !hideFooter : hideFooter,
}),
body: JSON.stringify({ hideNavbar, hideFooter, domain }),
});
const updatedSettings = await res.json();
setShowNotification(true);
setHideNavbar(updatedSettings.hideNavbar);
setHideFooter(updatedSettings.hideFooter);

if (updatedSettings.message) {
return setShowNotification({
show: true,
type: "error",
message: "Profile settings update failed",
additionalMessage: `Please check the fields: ${Object.keys(
updatedSettings.message,
).join(", ")}`,
});
}

return setShowNotification({
show: true,
type: "success",
message: "Profile updated",
additionalMessage: "Your profile settings has been updated successfully",
});
};

return (
Expand All @@ -84,28 +105,49 @@ export default function ManageSettings({
/>
)}
<Navigation />

<Notification
show={showNotification}
type="success"
onClose={() => setShowNotification(false)}
message="Premium updated"
additionalMessage="Your Profile Premium settings have been updated."
show={showNotification.show}
type={showNotification.type}
onClose={() =>
setShowNotification({ ...showNotification, show: false })
}
message={showNotification.message}
additionalMessage={showNotification.additionalMessage}
/>

{accountType !== "premium" && (
<Alert
type="warning"
message="These are Premium features. Please upgrade your account for these to take effect on your public Profile."
/>
)}
<form>
<form onSubmit={handleSubmit}>
<fieldset>
<legend className="sr-only">Premium features</legend>
<div className="sm:grid sm:grid-cols-3 sm:items-baseline sm:gap-4 sm:py-6">
<div
className="text-sm font-semibold leading-6 text-primary-medium dark:text-primary-low"
aria-hidden="true"
>
Premium features
<div>
<p
className="font-semibold leading-6 text-primary-medium dark:text-primary-low"
aria-hidden="true"
>
Premium features
</p>
{accountType === "premium" && (
<p
className="text-sm leading-6 text-primary-medium dark:text-primary-low mt-4"
aria-hidden="true"
>
For help with your Premium account settings:
<br />
<Link
href={`${PREMIUM_SUPPORT_URL} (${username})`}
target="_blank"
>
Contact Support
</Link>
</p>
)}
</div>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<div className="max-w-lg">
Expand All @@ -116,31 +158,39 @@ export default function ManageSettings({
<div className="flex items-center gap-x-3">
<Toggle
text1="Hide Navbar on your Profile"
enabled={accountType === "premium" && hideNavbar}
setEnabled={() => toggle("hideNavbar")}
enabled={enableForm && hideNavbar}
setEnabled={setHideNavbar}
/>
</div>
<div className="flex items-center gap-x-3">
<Toggle
text1="Hide Footer on your Profile"
enabled={accountType === "premium" && hideFooter}
setEnabled={() => toggle("hideFooter")}
enabled={enableForm && hideFooter}
setEnabled={setHideFooter}
/>
</div>
<div className="col-span-3 sm:col-span-4">
<div className="mt-1">
<Input
name="domain"
label="Domain"
value={domain}
disabled={!enableForm}
onChange={(e) => setDomain(e.target.value)}
/>
</div>
</div>
<div className="mt-10 border-t border-primary-low-medium/30 pt-6 sm:flex sm:items-center sm:justify-between">
<Button primary={true} disabled={!enableForm}>
SAVE
</Button>
</div>
</div>
</div>
</div>
</div>
</fieldset>
</form>
{accountType === "premium" && (
<p>
For help with your Premium account settings:{" "}
<Link href={`${PREMIUM_SUPPORT_URL} (${username})`} target="_blank">
Contact Support
</Link>
</p>
)}
</Page>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion pages/account/manage/repos.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default function ManageRepos({ BASE_URL, repos }) {
onChange={(e) => setUrl(e.target.value)}
value={url}
/>
<Button disable={!url.length}>
<Button disabled={!url.length}>
<DocumentPlusIcon className="h-5 w-5 mr-2" />
Add Repo
</Button>
Expand Down
2 changes: 1 addition & 1 deletion pages/account/onboarding/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export default function Onboarding({ profile, progress }) {
<Button
href={`/${profile.username}`}
className={"gap-4"}
disable={!cards[0].isEdit}
disabled={!cards[0].isEdit}
>
<FaArrowUpRightFromSquare className="w-4 h-4" /> View Profile
</Button>
Expand Down
Loading