diff --git a/.env.example.local b/.env.example.local
index 2ed5dfd..9e1a313 100644
--- a/.env.example.local
+++ b/.env.example.local
@@ -1,2 +1,6 @@
-NODE_MODE=development
-EXPRESS_API_URL=http://127.0.0.1:3100
\ No newline at end of file
+NODE_ENV=development
+NEXT_PUBLIC_EXPRESS_API_URL=http://127.0.0.1:3100
+NEXT_PUBLIC_RECAPTCHA_KEY=""
+NEXT_PUBLIC_GOOGLE_ANALYTICS_HOME=""
+NEXT_PUBLIC_GOOGLE_ANALYTICS_PAGES=""
+NEXT_PUBLIC_MICROSOFT_CLARITY=""
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
index 6894005..b303599 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -7,8 +7,6 @@
"useTabs": false,
"arrowParens": "avoid",
"tabWidth": 2,
- "plugins": [
- "prettier-plugin-tailwindcss"
- ],
- "pluginSearchDirs": false
+ "pluginSearchDirs": false,
+ "plugins": ["prettier-plugin-tailwindcss"]
}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index a6af7e2..dfe4799 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,7 +30,4 @@ RUN adduser --system --uid 1001 nextjs
RUN chown -R nextjs:nodejs /app/.next
USER nextjs
EXPOSE 3000
-CMD ["yarn", "start"]
-
-# create image with this command: sudo docker build . -t zoz.bio-image
-# run container with this command: sudo docker run -d --name zoz.bio --network npm zoz.bio-image
\ No newline at end of file
+CMD ["yarn", "start"]
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..447cd35
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,21 @@
+version: "3"
+services:
+ zoz.bio:
+ container_name: zoz.bio
+ build:
+ context: .
+ dockerfile: ./Dockerfile
+ restart: unless-stopped
+ environment:
+ NODE_ENV: production
+ networks:
+ - npm
+ deploy:
+ resources:
+ limits:
+ cpus: '2'
+ memory: '4GB'
+networks:
+ npm:
+ external: true
+ name: npm
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
index a145331..b0c6800 100644
--- a/next.config.js
+++ b/next.config.js
@@ -2,7 +2,7 @@
const nextConfig = {
reactStrictMode: false,
images: {
- domains: ["cdn.discordapp.com", "i.scdn.co", "live.staticflickr.com", "api.mapbox.com", "flowbite.s3.amazonaws.com"],
+ domains: ["api.zoz.bio", "127.0.0.1"],
},
};
diff --git a/package.json b/package.json
index 65f4bc7..678cdcc 100644
--- a/package.json
+++ b/package.json
@@ -22,13 +22,16 @@
"axios": "^1.4.0",
"clsx": "^1.2.1",
"cookies-next": "^2.1.1",
+ "css-doodle": "^0.34.9",
"next": "^13.4.4",
"next-auth": "^4.22.1",
"nextjs-toploader": "^1.4.2",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.2.0",
+ "react-google-recaptcha": "^3.1.0",
"react-hook-form": "^7.44.3",
+ "react-toastify": "^9.1.3",
"tailwind-merge": "^1.13.0",
"zod": "^3.21.4"
},
@@ -36,15 +39,20 @@
"@types/node": "20.2.5",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
+ "@types/react-google-recaptcha": "^2.1.5",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"color": "^4.2.3",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"postcss": "8.4.24",
- "prettier": "^2.8.8",
- "prettier-plugin-tailwindcss": "^0.3.0",
+ "prettier": "^3.0.3",
+ "prettier-plugin-tailwindcss": "^0.5.4",
"tailwindcss": "3.3.2",
"typescript": "5.0.4"
+ },
+ "resolutions": {
+ "@types/react": "18.2.7",
+ "@types/react-dom": "18.2.4"
}
}
diff --git a/public/banners/link-zelda.jpg b/public/banners/link-zelda.jpg
new file mode 100644
index 0000000..95278c4
Binary files /dev/null and b/public/banners/link-zelda.jpg differ
diff --git a/public/bg.svg b/public/bg.svg
new file mode 100644
index 0000000..7aa12c6
--- /dev/null
+++ b/public/bg.svg
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/confirm.gif b/public/confirm.gif
new file mode 100644
index 0000000..9858cba
Binary files /dev/null and b/public/confirm.gif differ
diff --git a/public/home.jpg b/public/home.jpg
new file mode 100644
index 0000000..84d20a6
Binary files /dev/null and b/public/home.jpg differ
diff --git a/public/home2.jpg b/public/home2.jpg
new file mode 100644
index 0000000..0b8c4d0
Binary files /dev/null and b/public/home2.jpg differ
diff --git a/public/home3.jpg b/public/home3.jpg
new file mode 100644
index 0000000..764036c
Binary files /dev/null and b/public/home3.jpg differ
diff --git a/public/icons/social/flickr.png b/public/icons/social/flickr.png
new file mode 100644
index 0000000..1b0e1db
Binary files /dev/null and b/public/icons/social/flickr.png differ
diff --git a/public/login.gif b/public/login.gif
new file mode 100644
index 0000000..28f8077
Binary files /dev/null and b/public/login.gif differ
diff --git a/public/metabg.png b/public/metabg.png
index 89bdf6a..ebf5f0e 100644
Binary files a/public/metabg.png and b/public/metabg.png differ
diff --git a/public/pfp.jpg b/public/pfp.jpg
new file mode 100644
index 0000000..9e0b553
Binary files /dev/null and b/public/pfp.jpg differ
diff --git a/public/register.png b/public/register.png
deleted file mode 100644
index 553c919..0000000
Binary files a/public/register.png and /dev/null differ
diff --git a/public/reset.gif b/public/reset.gif
new file mode 100644
index 0000000..753bfd9
Binary files /dev/null and b/public/reset.gif differ
diff --git a/public/teste.jpg b/public/teste.jpg
new file mode 100644
index 0000000..70f0af3
Binary files /dev/null and b/public/teste.jpg differ
diff --git a/public/zoz.png b/public/zoz.png
index 481c1e6..a123edd 100644
Binary files a/public/zoz.png and b/public/zoz.png differ
diff --git a/src/app/(BioLayout)/[username]/Bio.tsx b/src/app/(BioLayout)/[username]/Bio.tsx
index df25e5d..f95ec62 100644
--- a/src/app/(BioLayout)/[username]/Bio.tsx
+++ b/src/app/(BioLayout)/[username]/Bio.tsx
@@ -1,43 +1,67 @@
-"use client";
+import { PageProps } from "@/types/PageProps";
+import { defaultPage } from "@/utils/BioVariables";
+import { cookies } from "next/headers";
+import Image from "next/image";
import React from "react";
-import { useQuery } from "@tanstack/react-query";
-import { getPage } from "@/services/PageService";
-import BioMain from "./BioMain";
+import BioAvatar from "./BioAvatar";
+import BioBadges from "./BioBadges";
+import BioInfos from "./BioInfos";
+import BioLinks from "./BioLinks";
+import BioNavigation from "./BioNavigation";
+import BioSocials from "./BioSocials";
+import BioCard from "./BioCard";
+import CssDoodle from "@/components/CssDoodle/CssDoodle";
-export const BioComponent = ({ username }: { username: string }) => {
- const queryPage = useQuery({
- queryKey: ["getPage"],
- queryFn: () => getPage(username),
- });
-
- if (queryPage.isError) {
- const error = queryPage.error as Error;
- console.log(error);
- //TODO TOAST
- // errorToast(error.message);
- }
-
- if (queryPage.isLoading) {
- //TODO LOADING PAGE
- return
LOADING ....
;
- // return ;
- }
+export const BioComponent = ({ page }: { page: PageProps }) => {
+ const cookieStore = cookies();
+ const userCookie = cookieStore.get("zoz_user");
+ const user = userCookie ? JSON.parse(userCookie?.value) : undefined;
+ const backgroundUrl = page?.backgroundUrl || null;
+ const backGroundOpacity = page?.backGroundOpacity || defaultPage.bgOpacity;
return (
<>
- {queryPage.data?.page ? (
-
-
+
+ {backgroundUrl ? (
+
+
+
+ ) : (
+
+ )}
+
+
- ) : (
- // TODO 404 page
- <>
-
404
- {/*
-
-
*/}
- >
- )}
+
>
);
};
diff --git a/src/app/(BioLayout)/[username]/BioAvatar.tsx b/src/app/(BioLayout)/[username]/BioAvatar.tsx
new file mode 100644
index 0000000..3a17f9d
--- /dev/null
+++ b/src/app/(BioLayout)/[username]/BioAvatar.tsx
@@ -0,0 +1,49 @@
+import { PageProps } from "@/types/PageProps";
+import { defaultPage } from "@/utils/BioVariables";
+import { twMerge } from "tailwind-merge";
+import { memo } from "react";
+import Image from "next/image";
+
+type SectionCardProps = {
+ page?: PageProps;
+ className?: string;
+};
+
+const BioAvatar = ({ page }: SectionCardProps) => {
+ const pfpUrl = page?.pfpUrl || defaultPage.pfpUrl;
+ const primaryColor = page?.primaryColor || defaultPage.primaryColor;
+
+ return (
+
+
+
+
+ );
+};
+
+export default memo(BioAvatar);
diff --git a/src/app/(BioLayout)/[username]/BioBadges.tsx b/src/app/(BioLayout)/[username]/BioBadges.tsx
new file mode 100644
index 0000000..49769bd
--- /dev/null
+++ b/src/app/(BioLayout)/[username]/BioBadges.tsx
@@ -0,0 +1,32 @@
+import { PageProps } from "@/types/PageProps";
+import { defaultPage } from "@/utils/BioVariables";
+import { getBadge } from "@/utils/IconsList";
+import { memo } from "react";
+
+const BioBadges = ({ page }: { page: PageProps }) => {
+ const badges = page.badges || defaultPage.pageBadges;
+ const secondaryColor = page?.secondaryColor || defaultPage.secondaryColor;
+ const fontColor = page?.fontColor || defaultPage.fontColor;
+
+ if (badges && badges.length === 0) return null;
+
+ return (
+
+ {badges?.map((badge, idx) =>
+ getBadge(badge) ? (
+
+ {getBadge(badge)?.label}
+
+ ) : null
+ )}
+
+ );
+};
+export default memo(BioBadges);
diff --git a/src/app/(BioLayout)/[username]/BioCard.tsx b/src/app/(BioLayout)/[username]/BioCard.tsx
index fa0c592..a285138 100644
--- a/src/app/(BioLayout)/[username]/BioCard.tsx
+++ b/src/app/(BioLayout)/[username]/BioCard.tsx
@@ -1,17 +1,16 @@
import { PageProps } from "@/types/PageProps";
import { defaultPage } from "@/utils/BioVariables";
import { twMerge } from "tailwind-merge";
-import { memo } from "react";
+import { ReactNode, memo } from "react";
type SectionCardProps = {
- children: JSX.Element;
+ children: ReactNode;
page?: PageProps;
className?: string;
center?: boolean;
- bioPage?: boolean;
};
-const BioCard = ({ children, page, className, center = true, bioPage = true }: SectionCardProps) => {
+const BioCard = ({ children, page, className, center = true }: SectionCardProps) => {
const primaryColor = page?.primaryColor || defaultPage.primaryColor;
const cardBlur = page?.cardBlur || defaultPage.cardBlur;
const cardHueRotate = page?.cardHueRotate || defaultPage.cardHueRotate;
@@ -20,17 +19,15 @@ const BioCard = ({ children, page, className, center = true, bioPage = true }: S
className={twMerge(
cardBlur,
cardHueRotate,
- "relative flex flex-col",
- "mb-2 rounded-xl px-2 shadow-sm shadow-black sm:px-3",
+ "relative flex flex-row w-full rounded-xl shadow-sm shadow-black",
center ? "justify-center" : "justify-start",
- bioPage ? "w-full sm:w-5/6 md:w-3/4 lg:w-3/5 lg:max-w-2xl" : "",
className
)}
style={{
backgroundColor: `rgb(${primaryColor.r},${primaryColor.g},${primaryColor.b},${primaryColor.a})`,
}}
>
-
{children}
+ {children}
);
};
diff --git a/src/app/(BioLayout)/[username]/BioIFrames.tsx b/src/app/(BioLayout)/[username]/BioIFrames.tsx
new file mode 100644
index 0000000..c096153
--- /dev/null
+++ b/src/app/(BioLayout)/[username]/BioIFrames.tsx
@@ -0,0 +1,75 @@
+import { LinkProps } from "@/types/LinkProps";
+import { memo } from "react";
+
+// TODO - its kinda of laggy when switch folders if you hjave an iframe
+// seems like the iframe is reloading everytime, find a way to save the iframe, cache, etc
+// TODO - encapsulate this better
+// TODO - spotify albums
+const BioIFrames = ({ link }: { link: LinkProps }) => {
+ return (
+ <>
+ {link.embedded === "spotify" && link.isPlaylist ? (
+
+ ) : link.embedded === "spotify" ? (
+
+ ) : link.embedded === "soundcloud" && link.url.includes("/sets/") ? (
+
+ ) : link.embedded === "soundcloud" ? (
+
+ ) : link.embedded === "youtube" && link.url.length > 20 ? (
+
+ ) : link.embedded === "youtube" ? (
+
+ ) : null}
+ >
+ );
+};
+export default memo(BioIFrames);
diff --git a/src/app/(BioLayout)/[username]/BioIcon.tsx b/src/app/(BioLayout)/[username]/BioIcon.tsx
index ec12bd4..363cabb 100644
--- a/src/app/(BioLayout)/[username]/BioIcon.tsx
+++ b/src/app/(BioLayout)/[username]/BioIcon.tsx
@@ -1,25 +1,20 @@
+"use client";
import { memo } from "react";
-// import ZozTooltip from "../../components/Tooltip";
-// import { useToasts } from "../../context/ToastProvider/useToasts";
import { getSocialIcon } from "@/utils/IconsList";
+import { successToast } from "@/utils/toaster";
+import { Tooltip } from "@/components/Tooltip";
type MediaProps = {
username: string;
key: string;
};
-type PageIconProps = {
- media: MediaProps;
-};
-
-const PageIcon = ({ media }: PageIconProps) => {
+// TODO - next/image and try to ssr this
+const BioIcon = ({ media }: { media: MediaProps }) => {
const social = getSocialIcon(media.key);
- // const { successToast } = useToasts();
if (!social) return null;
return (
-
- {/* TODO TIPSY */}
- {/*
*/}
+
{social.url ? (
{
target="_blank"
rel="noopener noreferrer"
>
-
+
) : (
{
- // successToast(`Copied: ${media.username}`);
+ successToast(`Copied: ${media.username}`);
if (navigator.clipboard) {
navigator.clipboard.writeText(media.username);
}
@@ -43,8 +38,8 @@ const PageIcon = ({ media }: PageIconProps) => {
loading="lazy"
/>
)}
-
+
);
};
-export default memo(PageIcon);
+export default memo(BioIcon);
diff --git a/src/app/(BioLayout)/[username]/BioInfos.tsx b/src/app/(BioLayout)/[username]/BioInfos.tsx
index 8463a5e..3b3c487 100644
--- a/src/app/(BioLayout)/[username]/BioInfos.tsx
+++ b/src/app/(BioLayout)/[username]/BioInfos.tsx
@@ -1,55 +1,57 @@
import { memo } from "react";
-// import { useToasts } from "../../context/ToastProvider/useToasts";
import { PageProps } from "@/types/PageProps";
import { getAdornmentIcon } from "@/utils/IconsList";
+import CopyLabel from "@/components/Labels/CopyLabel";
+import { defaultPage } from "@/utils/BioVariables";
-type PageInfosProps = {
- page: PageProps;
-};
+const BioInfos = ({ page }: { page: PageProps }) => {
+ const fontColor = page?.fontColor || defaultPage.fontColor;
-const PageInfos = ({ page }: PageInfosProps) => {
- // const { successToast } = useToasts();
return (
- <>
-
-
+
+
+
{page?.uname || "No name~"}
- {page?.adornment ? (
-
- ) : null}
-
-
{
- // successToast(`Copied`);
- if (navigator.clipboard) {
- navigator.clipboard.writeText(`https://zoz.bio/${page?.pagename || ""}`);
- }
- }}
- >
- {`zoz.bio/${page?.pagename || ""}`}
+
+ {page?.adornment ? (
+
+ ) : null}
+
+
+
+
+
+ {page?.bio && (
{page?.bio}
-
- >
+ )}
+
);
};
-export default memo(PageInfos);
+export default memo(BioInfos);
diff --git a/src/app/(BioLayout)/[username]/BioLink.tsx b/src/app/(BioLayout)/[username]/BioLink.tsx
new file mode 100644
index 0000000..7b089de
--- /dev/null
+++ b/src/app/(BioLayout)/[username]/BioLink.tsx
@@ -0,0 +1,122 @@
+import { LinkProps } from "@/types/LinkProps";
+import { PageProps } from "@/types/PageProps";
+import { defaultPage } from "@/utils/BioVariables";
+import { getIcon } from "@/utils/IconsList";
+import clsx from "clsx";
+import Image from "next/image";
+import Link from "next/link";
+import { memo } from "react";
+
+type BioLinkProps = {
+ page: PageProps;
+ link: LinkProps;
+ setFolderOwner: (link: LinkProps) => void;
+};
+
+const BannerComponent = ({ link, fontColor }: { link: LinkProps; fontColor: string }) => {
+ const typeCover = link.bannerUrl || "";
+
+ return (
+ <>
+ {typeCover && (
+
+ )}
+
+
+
{link.isFolder ? "Folder" : "Link"}
+
+ >
+ );
+};
+
+const LinkComponent = ({ page, link, setFolderOwner }: BioLinkProps) => {
+ const fontColor = page?.fontColor || defaultPage.fontColor;
+ const h2ClassName =
+ "sm:ml-7 flex-1 flex-shrink-0 truncate whitespace-pre-wrap text-center font-bold tracking-wide overflow-visible whitespace-nowrap text-lg sm:text-xl";
+ if (link.isFolder)
+ return (
+ {
+ link.isSelected = true;
+ setFolderOwner(link);
+ }}
+ >
+
+ {/* TODO - UX upgrade */}
+ {link.isSelected ? "Click to go back" : link.label}
+
+
+ );
+ return (
+
+
+ {link.label}
+
+
+ );
+};
+
+const BioLink = ({ page, link, setFolderOwner }: BioLinkProps) => {
+ const primaryColor = page?.primaryColor || defaultPage.primaryColor;
+ const fontColor = page?.fontColor || defaultPage.fontColor;
+ const cardBlur = page?.cardBlur || defaultPage.cardBlur;
+ const cardHueRotate = page?.cardHueRotate || defaultPage.cardHueRotate;
+
+ const cardStyle = {
+ backgroundColor: `rgb(${primaryColor.r},${primaryColor.g},${primaryColor.b},${primaryColor.a})`,
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default memo(BioLink);
diff --git a/src/app/(BioLayout)/[username]/BioLinks.tsx b/src/app/(BioLayout)/[username]/BioLinks.tsx
index ba131de..66df956 100644
--- a/src/app/(BioLayout)/[username]/BioLinks.tsx
+++ b/src/app/(BioLayout)/[username]/BioLinks.tsx
@@ -1,15 +1,16 @@
-import SectionCard from "./BioCard";
-import { memo, useState } from "react";
-import { getIcon } from "@/utils/IconsList";
-import { PageProps } from "@/types/PageProps";
+"use client";
import { LinkProps } from "@/types/LinkProps";
-import { ArrowUpRightIcon, ArrowRightIcon, ArrowLeftIcon } from "@heroicons/react/20/solid";
+import { PageProps } from "@/types/PageProps";
+import { memo, useState } from "react";
+import BioIFrames from "./BioIFrames";
+import BioLink from "./BioLink";
+import BioCard from "./BioCard";
-type PageLinksProps = {
+type BioLinksProps = {
page: PageProps;
};
-const PageLinks = ({ page }: PageLinksProps) => {
+const BioLinks = ({ page }: BioLinksProps) => {
const [folderOwner, setFolderOwner] = useState();
const pageLinks = page?.pageLinks
@@ -29,151 +30,33 @@ const PageLinks = ({ page }: PageLinksProps) => {
return a.position - b.position;
})
: [];
-
return (
<>
{folderOwner ? (
-
- setFolderOwner(null)}
- >
-
-
- {folderOwner.label}
-
-
Click to go back
-
-
-
+
+ {
+ link.isSelected = false;
+ setFolderOwner(null);
+ }}
+ />
+
) : null}
{pageLinks.map((link, idx) =>
link.embedded === "none" ? (
- <>
- {link.isFolder ? (
-
- setFolderOwner(link)}
- >
-
-
- {link.label}
-
-
Click to open
-
-
-
- ) : (
-
-
-
-
- {link.label}
-
- {link.url}
-
-
-
- )}
- >
+
+
+
) : (
- <>
-
- {link.embedded === "spotify" && link.url.includes("playlist") ? (
-
- ) : link.embedded === "spotify" ? (
-
- ) : link.embedded === "soundcloud" && link.url.includes("/sets/") ? (
-
- ) : link.embedded === "soundcloud" ? (
-
- ) : link.embedded === "youtube" && link.url.length > 20 ? (
-
- ) : link.embedded === "youtube" ? (
-
- ) : null}
-
- >
+
+
+
)
)}
>
);
};
-export default memo(PageLinks);
+export default memo(BioLinks);
diff --git a/src/app/(BioLayout)/[username]/BioMain.tsx b/src/app/(BioLayout)/[username]/BioMain.tsx
deleted file mode 100644
index d369e8f..0000000
--- a/src/app/(BioLayout)/[username]/BioMain.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { memo } from "react";
-import PageIcon from "./BioIcon";
-import SectionCard from "./BioCard";
-import { PageProps, PagePropsSocialMedia, PagePropsStatus } from "@/types/PageProps";
-import { getBadge, getStatusIcon } from "@/utils/IconsList";
-// import { useAuth } from "../../context/AuthProvider/useAuth";
-// import { Cog6ToothIcon } from "@heroicons/react/20/solid";
-// import { BigHead } from "@bigheads/core";
-import { defaultPage, setCssVariables } from "@/utils/BioVariables";
-import PageInfos from "./BioInfos";
-import "./Page.css";
-import PageLinks from "./BioLinks";
-import { LazyLoadImage } from "@/components/Loadings";
-
-const mapSocials = (pageSocialMedias: PagePropsSocialMedia[]) => {
- return (
-
- {pageSocialMedias.map((media, idx) => (
-
- ))}
-
- );
-};
-
-const mapBadges = (pageBadges: string[]) => {
- return (
-
- {pageBadges.map((badge, idx) =>
- getBadge(badge) ? (
-
- {getBadge(badge)?.label}
-
- ) : null
- )}
-
- );
-};
-
-const getPageStatus = (status: PagePropsStatus) => {
- const statusIcon = getStatusIcon(status.key);
- return statusIcon ? (
-
-
-
- ) : null;
-};
-
-const getAvatar = (pfpUrl: string | undefined) => {
- return (
-
- {pfpUrl ? (
-
- ) : (
-
- {/* */}
-
- )}
-
- );
-};
-
-// TODO
-// blur effect on loading maybe? blur-sm
-const BioMain = ({ page }: { page: PageProps }) => {
- // const auth = useAuth();
- const primaryColor = page?.primaryColor || defaultPage.primaryColor;
- const secondaryColor = page?.secondaryColor || defaultPage.secondaryColor;
- const fontColor = page?.fontColor || defaultPage.fontColor;
- const pfpUrl = page?.pfpUrl || undefined;
- const backgroundUrl = page?.backgroundUrl || defaultPage.bgUrl;
- const backgroundSize = page?.backgroundSize || defaultPage.bgSize;
- const backGroundOpacity = page?.backGroundOpacity || defaultPage.bgOpacity;
- const pageSocialMedias = page?.socialMedias?.length > 0 ? page.socialMedias : defaultPage.pageSocialMedias;
-
- const pageBadges = page?.badges?.length > 0 ? page.badges : defaultPage.pageBadges;
- const pageStatus = page?.status || defaultPage.pageStatus;
- setCssVariables(primaryColor, secondaryColor, fontColor);
-
- return (
- <>
- {/* Link to Account settings */}
- {/* TODO MELHORAR ISSO AQUI */}
- {/* {auth && auth.email ? (
-
-
-
- ) : null} */}
- {/* Page Background */}
- {/* TODO VER ISSO AQUI EM */}
-
-
-
- {/* Page Primary Card */}
-
- <>
- {getPageStatus(pageStatus)}
- {getAvatar(pfpUrl)}
-
-
- {mapBadges(pageBadges)}
- {mapSocials(pageSocialMedias)}
-
- >
-
- {/* Page Other Cards */}
-
- {/* Bottom home link */}
-
-
- >
- );
-};
-
-export default memo(BioMain);
diff --git a/src/app/(BioLayout)/[username]/BioNavigation.tsx b/src/app/(BioLayout)/[username]/BioNavigation.tsx
new file mode 100644
index 0000000..2b94b91
--- /dev/null
+++ b/src/app/(BioLayout)/[username]/BioNavigation.tsx
@@ -0,0 +1,47 @@
+"use client";
+import { PageProps } from "@/types/PageProps";
+import { memo } from "react";
+import { HomeIcon, Cog6ToothIcon, ExclamationTriangleIcon } from "@heroicons/react/20/solid";
+import { Link } from "@/components/Buttons";
+import { UserProps } from "@/types/UserProps";
+
+const BioNavigation = ({ page, user, editPage }: { page: PageProps; user: UserProps; editPage?: boolean }) => {
+ //TODO - make a better visual here, make report dialog
+ if (page && user)
+ return (
+
+
+
+
+ Account
+
+
+ {!editPage && (
+
console.log("click")}
+ className="group rounded-xl text-red-500 bg-primary/70 px-2 hover:text-red"
+ >
+
+
+ Report
+
+
+ )}
+
+ );
+
+ return (
+
+
+
+
+ zoz.bio
+ create your bio page
+
+
+
+ );
+};
+export default memo(BioNavigation);
diff --git a/src/app/(BioLayout)/[username]/BioSocials.tsx b/src/app/(BioLayout)/[username]/BioSocials.tsx
new file mode 100644
index 0000000..55bbf0b
--- /dev/null
+++ b/src/app/(BioLayout)/[username]/BioSocials.tsx
@@ -0,0 +1,14 @@
+import { memo } from "react";
+import BioIcon from "./BioIcon";
+import { PageProps } from "@/types/PageProps";
+import { defaultPage } from "@/utils/BioVariables";
+
+const BioSocials = ({ page }: { page: PageProps }) => {
+ const socialMedias = page?.socialMedias?.length > 0 ? page.socialMedias : defaultPage.pageSocialMedias;
+ return (
+
+ {socialMedias && socialMedias.map((media, idx) => )}
+
+ );
+};
+export default memo(BioSocials);
diff --git a/src/app/(BioLayout)/[username]/BioStatusIcon.tsx b/src/app/(BioLayout)/[username]/BioStatusIcon.tsx
new file mode 100644
index 0000000..1636679
--- /dev/null
+++ b/src/app/(BioLayout)/[username]/BioStatusIcon.tsx
@@ -0,0 +1,14 @@
+import { memo } from "react";
+import { PageStatusProps } from "@/types/PageProps";
+import { getStatusIcon } from "@/utils/IconsList";
+
+const BioStatusIcon = ({ status }: { status: PageStatusProps }) => {
+ const statusIcon = getStatusIcon(status.key);
+ return statusIcon ? (
+
+
+
+ ) : null;
+};
+
+export default memo(BioStatusIcon);
diff --git a/src/app/(BioLayout)/[username]/NotFound.tsx b/src/app/(BioLayout)/[username]/NotFound.tsx
new file mode 100644
index 0000000..2a254d5
--- /dev/null
+++ b/src/app/(BioLayout)/[username]/NotFound.tsx
@@ -0,0 +1,4 @@
+// TODO
+export const NotFound = ({ username }: { username: string }) => {
+ return {username} - NotFound
;
+};
diff --git a/src/app/(BioLayout)/[username]/layout.tsx b/src/app/(BioLayout)/[username]/layout.tsx
index 7e65201..f455fce 100644
--- a/src/app/(BioLayout)/[username]/layout.tsx
+++ b/src/app/(BioLayout)/[username]/layout.tsx
@@ -1,34 +1,17 @@
// import Script from "next/script";
-import axios from "axios";
-import { PageProps } from "@/types/PageProps";
-import { ZOZ_META_DESCRIPTION, ZOZ_META_TITLE } from "@/utils/Constants";
-import "@/app/globals.css";
+import "@/app/(BioLayout)/biolayout.css";
+import "react-toastify/dist/ReactToastify.css";
+import "tippy.js/animations/perspective.css";
+import "tippy.js/dist/tippy.css";
+import "tippy.js/themes/translucent.css";
+import ToastProvider from "@/providers/ToastProvider";
+import { ReactNode } from "react";
+import Analytics from "@/components/Analytics";
-export async function generateMetadata({ params }: { params: { username: string; page: PageProps } }) {
- const res = await axios.get("http://127.0.0.1:3100/page", {
- params: { pagename: params.username },
- });
+// METADATA EXAMPLE - https://nextjs.org/docs/app/api-reference/functions/generate-metadata
- if (res && res.data?.page) {
- params.page = res.data.page;
- return {
- title: `Username: ${params.username}`,
- description: res.data.page.bio || ZOZ_META_DESCRIPTION,
- // openGraph: {
- // images: ['/some-specific-page-image.jpg', ...previousImages],
- // },
- };
- }
-
- return {
- title: ZOZ_META_TITLE,
- description: ZOZ_META_DESCRIPTION,
- };
-}
-
-export default function BioLayout({ children }: { children: React.ReactNode }) {
- //TODO OG GRAPH IMAGE TWITTER E FB
- //TODO GOOGLE ANALYTICS
+export default function BioLayout({ children }: { children: ReactNode }) {
+ //TODO - OG GRAPH IMAGE TWITTER E FB
return (
@@ -39,7 +22,16 @@ export default function BioLayout({ children }: { children: React.ReactNode }) {
rel="stylesheet"
/>
- {children}
+
+ {process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_PAGES && process.env.NEXT_PUBLIC_MICROSOFT_CLARITY && (
+
+ )}
+ {children}
+
+
);
}
diff --git a/src/app/(BioLayout)/[username]/page.css b/src/app/(BioLayout)/[username]/page.css
deleted file mode 100644
index c81afd3..0000000
--- a/src/app/(BioLayout)/[username]/page.css
+++ /dev/null
@@ -1,28 +0,0 @@
-.icon-shadow {
- -webkit-filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 1));
- filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 1));
-}
-
-.page-font-color {
- color: var(--page-font-color);
-}
-
-.hsecondary {
- color: var(--page-font-color);
- opacity: 0.5;
-}
-
-.hsecondary:hover {
- color: var(--page-secondary-color);
- opacity: 1;
-}
-
-.ring-avatar {
- --tw-ring-color: var(--page-secondary-color);
- border-color: var(--page-secondary-color);
-}
-
-.ring-badges {
- color: var(--page-font-color);
- --tw-ring-color: var(--page-secondary-color);
-}
diff --git a/src/app/(BioLayout)/[username]/page.tsx b/src/app/(BioLayout)/[username]/page.tsx
index c52a07a..9b0e08e 100644
--- a/src/app/(BioLayout)/[username]/page.tsx
+++ b/src/app/(BioLayout)/[username]/page.tsx
@@ -1,14 +1,39 @@
import { QueryClientProviderComponent } from "@/providers/QueryClientProvider";
+import { fetchBioPage } from "@/services/PageService";
+import { PageProps } from "@/types/PageProps";
+import { ZOZ_META_DESCRIPTION, ZOZ_META_TITLE } from "@/utils/Constants";
import { BioComponent } from "./Bio";
+import { NotFound } from "./NotFound";
+
+let pageData: PageProps | undefined = undefined;
+
+export async function generateMetadata({ params }: { params: { username: string } }) {
+ if (pageData) {
+ return {
+ // TODO - generate profile img, change title
+ title: `zoz.bio - ${params.username}`,
+ description: pageData.bio || ZOZ_META_DESCRIPTION,
+ // openGraph: {
+ // images: ['/some-specific-page-image.jpg', ...previousImages],
+ // },
+ };
+ }
+
+ return {
+ title: ZOZ_META_TITLE,
+ description: ZOZ_META_DESCRIPTION,
+ };
+}
+
+export default async function BioPage({ params }: { params: { username: string } }) {
+ const res = await fetchBioPage(params.username);
+ pageData = res?.page;
-export default function BioPage({ params }: { params: { username: string } }) {
return (
- <>
-
-
-
-
-
- >
+
+
+ {pageData ? : }
+
+
);
}
diff --git a/src/app/(BioLayout)/biolayout.css b/src/app/(BioLayout)/biolayout.css
new file mode 100644
index 0000000..26d79fd
--- /dev/null
+++ b/src/app/(BioLayout)/biolayout.css
@@ -0,0 +1,81 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --foreground-rgb: 255, 255, 255;
+ --background-start-rgb: 0, 0, 0;
+ --background-end-rgb: 0, 0, 0;
+
+ font-family: Inter, Avenir, Helvetica, sans-serif;
+ font-size: clamp(0.8rem, 1vw, 1rem);
+ font-weight: 500;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+/* Scrollbar */
+
+::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+ background: transparent;
+}
+
+/* */
+
+html {
+ overflow: scroll;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+
+body {
+ /* font-family: Roboto, sans-serif; */
+ color: rgb(var(--foreground-rgb));
+ background-color: #141618;
+}
+
+button:focus,
+a:focus,
+button:focus-visible,
+a:focus-visible {
+ outline: 0ch;
+ --tw-ring-offset-width: 0px !important;
+}
+
+.icon-shadow {
+ -webkit-filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 1));
+ filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 1));
+}
+
+.arrow-card-start {
+ -webkit-clip-path: polygon(0% 0%, 70% 0%, 100% 50%, 70% 100%, 0% 100%);
+ clip-path: polygon(0% 0%, 70% 0%, 100% 50%, 70% 100%, 0% 100%);
+}
+
+.arrow-card-end {
+ -webkit-clip-path: polygon(100% 0, 100% 50%, 100% 100%, 0% 100%, 6% 50%, 0% 0%);
+ clip-path: polygon(100% 0, 100% 50%, 100% 100%, 0% 100%, 6% 50%, 0% 0%);
+}
+
+.home-image-fade {
+ -webkit-mask-image:-webkit-gradient(linear, left top, right bottom, from(rgba(0,0,0,0)), to(rgba(0,0,0,1)));
+ mask-image: linear-gradient(to left, rgba(0,0,0,1), rgba(0,0,0,0));
+}
+
+@screen sm {
+ .arrow-card-start {
+ -webkit-clip-path: polygon(0% 0%, 85% 0%, 100% 50%, 85% 100%, 0% 100%);
+ clip-path: polygon(0% 0%, 85% 0%, 100% 50%, 85% 100%, 0% 100%);
+ }
+
+ .arrow-card-avatar {
+ -webkit-clip-path: polygon(0 0, 100% 0%, 75% 100%, 0% 100%);
+ clip-path: polygon(0 0, 100% 0%, 75% 100%, 0% 100%);
+ }
+}
\ No newline at end of file
diff --git a/src/app/(BioLayout)/edit/[username]/AutoCompleteFolders.tsx b/src/app/(BioLayout)/edit/[username]/AutoCompleteFolders.tsx
new file mode 100644
index 0000000..4275f5a
--- /dev/null
+++ b/src/app/(BioLayout)/edit/[username]/AutoCompleteFolders.tsx
@@ -0,0 +1,64 @@
+import React from "react";
+import { useQuery } from "@tanstack/react-query";
+import { getFolders } from "@/services/LinkService";
+import { errorToast } from "@/utils/toaster";
+import { MediaIconProps } from "@/types/MediaIconProps";
+import { AutoComplete } from "@/components/Inputs";
+import { LinkProps } from "@/types/LinkProps";
+import { getIcon } from "@/utils/IconsList";
+
+type AutoCompleteFoldersProps = {
+ label: string;
+ pagename: string;
+ disabled?: boolean;
+ iconAdornment?: JSX.Element;
+ selected?: string;
+ setSelected: (value: string) => void;
+};
+
+const AutoCompleteFolders = ({
+ label,
+ disabled = false,
+ pagename,
+ selected = "",
+ setSelected,
+}: AutoCompleteFoldersProps) => {
+ const queryPage = useQuery({
+ queryKey: ["getFolders"],
+ queryFn: () => getFolders(pagename),
+ });
+
+ if (queryPage.isError) {
+ errorToast(queryPage.error as Error);
+ }
+
+ const list = new Map([]);
+ list.set("", {
+ icon: getIcon("banned")?.icon || "",
+ label: "None",
+ });
+
+ if (queryPage.data?.folders) {
+ queryPage.data.folders.map((folder: LinkProps) => {
+ list.set(folder._id, {
+ icon: getIcon(folder.icon)?.icon || "",
+ label: folder.label,
+ });
+ });
+ }
+
+ return (
+
+ );
+};
+
+export default React.memo(AutoCompleteFolders);
diff --git a/src/app/(BioLayout)/edit/[username]/ButtonCard.tsx b/src/app/(BioLayout)/edit/[username]/ButtonCard.tsx
new file mode 100644
index 0000000..a56a5a7
--- /dev/null
+++ b/src/app/(BioLayout)/edit/[username]/ButtonCard.tsx
@@ -0,0 +1,40 @@
+import { PageProps } from "@/types/PageProps";
+import { defaultPage } from "@/utils/BioVariables";
+import { ReactNode, memo } from "react";
+import { twMerge } from "tailwind-merge";
+
+type ButtonCardProps = {
+ label: string;
+ page: PageProps;
+ onClick: () => void;
+ className?: string;
+ iconAdornment?: ReactNode;
+};
+
+const ButtonCard = ({ label, className, page, onClick, iconAdornment }: ButtonCardProps) => {
+ const cardBlur = page?.cardBlur || defaultPage.cardBlur;
+ const cardHueRotate = page?.cardHueRotate || defaultPage.cardHueRotate;
+ const primaryColor = page?.primaryColor || defaultPage.primaryColor;
+ const fontColor = page?.fontColor || defaultPage.fontColor;
+
+ return (
+
+ {iconAdornment}
+ {label}
+
+ );
+};
+export default memo(ButtonCard);
diff --git a/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditBadges.tsx b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditBadges.tsx
new file mode 100644
index 0000000..f0049d7
--- /dev/null
+++ b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditBadges.tsx
@@ -0,0 +1,83 @@
+import { Button } from "@/components/Buttons";
+import Dialog from "@/components/Dialogs";
+import { saveBadges } from "@/services/PageService";
+import { PageProps } from "@/types/PageProps";
+import { badgeList } from "@/utils/IconsList";
+import { errorToast } from "@/utils/toaster";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { memo } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+
+const createBadgesFormSchema = z.object({
+ badges: z.string().array(),
+});
+
+type CreateBadgesFormData = z.infer;
+
+type DialogEditBadgesProps = {
+ isOpen: boolean;
+ page: PageProps;
+ setIsOpen: (value: boolean) => void;
+};
+
+const DialogEditBadges = ({ isOpen, page, setIsOpen }: DialogEditBadgesProps) => {
+ const submitPageBadges = (data: CreateBadgesFormData) => {
+ if (data.badges.length <= 10) {
+ saveBadges(data.badges, page.pagename)
+ .then(() => {
+ setIsOpen(false);
+ window.location.reload();
+ })
+ .catch(error => {
+ errorToast(error);
+ });
+ } else {
+ errorToast("You can only show a maximum of 10 badges");
+ }
+ };
+
+ const {
+ watch,
+ handleSubmit,
+ setValue,
+ formState: { isSubmitting },
+ } = useForm({
+ resolver: zodResolver(createBadgesFormSchema),
+ defaultValues: { badges: page.badges || [] },
+ });
+
+ const pageBadges = watch("badges");
+ return (
+
+
+
+ );
+};
+
+export default memo(DialogEditBadges);
diff --git a/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditInfos.tsx b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditInfos.tsx
new file mode 100644
index 0000000..7afd744
--- /dev/null
+++ b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditInfos.tsx
@@ -0,0 +1,130 @@
+import { Button } from "@/components/Buttons";
+import Dialog from "@/components/Dialogs";
+import { Input } from "@/components/Inputs";
+import { checkPagename, savePageInfos } from "@/services/PageService";
+import { PageProps } from "@/types/PageProps";
+import { errorToast } from "@/utils/toaster";
+import { CheckIcon, XMarkIcon } from "@heroicons/react/20/solid";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useEffect, useState } from "react";
+import { memo } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+
+const createInfosFormSchema = z.object({
+ uname: z.string().nonempty("Display Name is required").min(1, "Display Name must have at least 4 characters"),
+ bio: z.string(),
+ pagename: z
+ .string()
+ .nonempty("Page name is required")
+ .min(4, "Page name must have at least 4 characters")
+ .refine(value => /^[a-zA-Z0-9_.]+$/.test(value), "Page Name allows only alphabets, numbers, _ or ."),
+});
+
+type CreateInfosFormData = z.infer;
+
+type DialogEditInfosProps = {
+ isOpen: boolean;
+ page: PageProps;
+ setIsOpen: (value: boolean) => void;
+};
+
+const DialogEditInfos = ({ isOpen, setIsOpen, page }: DialogEditInfosProps) => {
+ const [newPagename, setNewPagename] = useState("");
+ const [isPagenameAvailable, setPagenameAvailable] = useState(true);
+
+ useEffect(() => {
+ const pagenameQuery = setTimeout(() => {
+ if (page?.pagename && newPagename === page.pagename) {
+ setPagenameAvailable(true);
+ } else if (newPagename && newPagename.length > 0)
+ checkPagename(newPagename)
+ .then(response => {
+ setPagenameAvailable(response.isAvailable);
+ })
+ .catch(error => {
+ errorToast(error);
+ });
+ }, 300);
+ return () => {
+ clearTimeout(pagenameQuery);
+ };
+ }, [newPagename]);
+
+ const submitPageInfos = (data: CreateInfosFormData) => {
+ savePageInfos(data.uname, data.bio, page.pagename, newPagename?.length > 1 ? newPagename : page.pagename)
+ .then(() => {
+ setIsOpen(false);
+ window.location.reload();
+ })
+ .catch(err => {
+ errorToast(err);
+ });
+ };
+
+ const {
+ watch,
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(createInfosFormSchema),
+ defaultValues: { uname: page?.uname, bio: page?.bio, pagename: page?.pagename },
+ });
+
+ if (watch("pagename") !== newPagename) setNewPagename(watch("pagename"));
+
+ return (
+
+
+
+ );
+};
+
+export default memo(DialogEditInfos);
diff --git a/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditLink.tsx b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditLink.tsx
new file mode 100644
index 0000000..9810708
--- /dev/null
+++ b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditLink.tsx
@@ -0,0 +1,198 @@
+import { Button } from "@/components/Buttons";
+import Dialog from "@/components/Dialogs/Dialog";
+import { Input, RadioGroup } from "@/components/Inputs";
+import { deleteLink, updateLink } from "@/services/LinkService";
+import { LinkProps } from "@/types/LinkProps";
+import { PageProps } from "@/types/PageProps";
+import { errorToast } from "@/utils/toaster";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { memo, useState } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import AutoCompleteFolders from "../AutoCompleteFolders";
+import ConfirmationDialog from "@/components/Dialogs/ConfirmationDialog";
+
+type DialogEditLinkProps = {
+ page: PageProps;
+ link: LinkProps;
+ setSelectedLink: (value: LinkProps | null) => void;
+};
+
+const createLinkFormSchema = z.object({
+ _id: z.string(),
+ url: z.string().nonempty("Url is required"),
+ label: z.string().nonempty("Link label is required"),
+ embedded: z.string().nonempty(),
+ isFolder: z.boolean(),
+ folderOwner: z.string().nullish(),
+});
+
+// TODO - add banner to form
+const DialogEditLink = ({ page, link, setSelectedLink }: DialogEditLinkProps) => {
+ const {
+ watch,
+ register,
+ setValue,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(createLinkFormSchema),
+ defaultValues: {
+ _id: link._id,
+ url: link.url,
+ label: link.label,
+ embedded: link.embedded,
+ isFolder: link.isFolder,
+ folderOwner: link.folderOwner,
+ },
+ });
+ const [confirmationDialog, setConfirmationDialog] = useState(false);
+
+ const submitLinkInfos = (data: LinkProps) => {
+ updateLink(data, page.pagename)
+ .then(() => {
+ setSelectedLink(null);
+ window.location.reload();
+ })
+ .catch(error => {
+ errorToast(error);
+ });
+ };
+
+ const isFolder = watch("isFolder");
+ return (
+ {
+ setSelectedLink(null);
+ }}
+ >
+
+ {
+ deleteLink(watch("_id"), page.pagename)
+ .then(() => {
+ setSelectedLink(null);
+ window.location.reload();
+ })
+ .catch(error => {
+ errorToast(error);
+ });
+ }}
+ />
+
+ );
+};
+
+export default memo(DialogEditLink);
diff --git a/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditSocials.tsx b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditSocials.tsx
new file mode 100644
index 0000000..9494775
--- /dev/null
+++ b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogEditSocials.tsx
@@ -0,0 +1,186 @@
+import { memo, useEffect, useState } from "react";
+import { PageProps, PageSocialMediaProps } from "@/types/PageProps";
+import { PlusIcon, XMarkIcon } from "@heroicons/react/20/solid";
+import Dialog from "@/components/Dialogs";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { errorToast, successToast } from "@/utils/toaster";
+import { saveSocialMedia } from "@/services/PageService";
+import { Button } from "@/components/Buttons";
+import BioIcon from "@/app/(BioLayout)/[username]/BioIcon";
+import { getSocialIcon, socialIconsList } from "@/utils/IconsList";
+import { AutoComplete, Input } from "@/components/Inputs";
+
+const createSocialsFormSchema = z.object({
+ account: z.string().nonempty("This field is a required"),
+});
+
+type CreateSocialsFormData = z.infer;
+
+type DialogEditSocialsProps = {
+ isOpen: boolean;
+ page: PageProps;
+ setIsOpen: (value: boolean) => void;
+};
+
+// TODO - refactor
+const DialogEditSocials = ({ isOpen, page, setIsOpen }: DialogEditSocialsProps) => {
+ const [items, setItems] = useState();
+ const [mediaSelected, setMediaSelected] = useState("discord");
+
+ useEffect(() => {
+ if (isOpen && page && page.socialMedias) {
+ setItems(Object.assign([], page.socialMedias));
+ }
+ }, [isOpen]);
+
+ const submitNewSocial = (data: CreateSocialsFormData) => {
+ if (data.account && items && items.length < 25) {
+ if (items?.some(item => item.key === mediaSelected)) {
+ errorToast("This account has already been added!");
+ } else {
+ const newItems = Object.assign([], items);
+ newItems?.push({
+ key: mediaSelected,
+ username: data.account,
+ });
+ setItems(newItems);
+ }
+ }
+ };
+
+ const {
+ watch,
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(createSocialsFormSchema),
+ defaultValues: { account: "" },
+ });
+
+ const account = watch("account");
+
+ return (
+
+
+
+ );
+};
+
+export default memo(DialogEditSocials);
diff --git a/src/app/(BioLayout)/edit/[username]/Dialogs/DialogNewLink.tsx b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogNewLink.tsx
new file mode 100644
index 0000000..9bc394a
--- /dev/null
+++ b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogNewLink.tsx
@@ -0,0 +1,150 @@
+import { Button } from "@/components/Buttons";
+import Dialog from "@/components/Dialogs/Dialog";
+import { Input, RadioGroup } from "@/components/Inputs";
+import { createLink } from "@/services/LinkService";
+import { LinkProps } from "@/types/LinkProps";
+import { PageProps } from "@/types/PageProps";
+import { errorToast } from "@/utils/toaster";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { memo } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import AutoCompleteFolders from "../AutoCompleteFolders";
+
+type DialogNewLinkProps = {
+ isOpen: boolean;
+ page: PageProps;
+ setIsOpen: (value: boolean) => void;
+};
+
+const createLinkFormSchema = z.object({
+ url: z.string().nonempty("Url is required"),
+ label: z.string().nonempty("Link label is required"),
+ embedded: z.string().nonempty(),
+ isFolder: z.boolean(),
+ folderOwner: z.string(),
+});
+
+// TODO - add banner to form
+const DialogNewLink = ({ isOpen, page, setIsOpen }: DialogNewLinkProps) => {
+ const {
+ watch,
+ register,
+ setValue,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(createLinkFormSchema),
+ defaultValues: { url: "", label: "", embedded: "none", isFolder: false, folderOwner: "" },
+ });
+
+ const submitLinkInfos = (data: LinkProps) => {
+ createLink(data, page.pagename)
+ .then(() => {
+ setIsOpen(false);
+ window.location.reload();
+ })
+ .catch(error => {
+ errorToast(error);
+ });
+ };
+
+ const isFolder = watch("isFolder");
+ return (
+
+
+
+ );
+};
+
+export default memo(DialogNewLink);
diff --git a/src/app/(RootLayout)/account/DialogNewPage.tsx b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogNewPage.tsx
similarity index 64%
rename from src/app/(RootLayout)/account/DialogNewPage.tsx
rename to src/app/(BioLayout)/edit/[username]/Dialogs/DialogNewPage.tsx
index ced8ea7..59c03e5 100644
--- a/src/app/(RootLayout)/account/DialogNewPage.tsx
+++ b/src/app/(BioLayout)/edit/[username]/Dialogs/DialogNewPage.tsx
@@ -1,14 +1,14 @@
-import { memo, useEffect, useState } from "react";
-// import { useToasts } from "../../context/ToastProvider/useToasts";
-import { PageProps } from "@/types/PageProps";
-import { CheckIcon, XMarkIcon } from "@heroicons/react/20/solid";
+import { Button } from "@/components/Buttons";
+import Dialog from "@/components/Dialogs";
import { Input } from "@/components/Inputs";
import { checkPagename, createPage } from "@/services/PageService";
-import { Button } from "@/components/Buttons";
+import { PageProps } from "@/types/PageProps";
+import { errorToast, successToast } from "@/utils/toaster";
+import { CheckIcon, XMarkIcon } from "@heroicons/react/20/solid";
import { zodResolver } from "@hookform/resolvers/zod";
+import { memo, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
-import Dialog from "@/components/Dialogs";
type DialogNewPageProps = {
isOpen: boolean;
@@ -41,7 +41,7 @@ const createPageFormSchema = z.object({
.string()
.nonempty("Page name is required")
.min(4, "Page name must have at least 4 characters")
- .refine(value => /^[a-zA-Z]+[-'s]?[a-zA-Z ]+$/.test(value), "Name should contain only alphabets"),
+ .refine(value => /^[a-zA-Z0-9_.]+$/.test(value), "Page Name allows only alphabets, numbers, _ or ."),
});
type CreatePageFormData = z.infer;
@@ -50,7 +50,6 @@ const DialogNewPage = ({ isOpen, setIsOpen, addNewPage }: DialogNewPageProps) =>
const [pagename, setPagename] = useState("");
const [isPagenameAvailable, setPagenameAvailable] = useState(true);
const [examplePagename, setExamplePagename] = useState("");
- // const { errorToast, successToast } = useToasts();
const {
watch,
@@ -77,9 +76,8 @@ const DialogNewPage = ({ isOpen, setIsOpen, addNewPage }: DialogNewPageProps) =>
.then(response => {
setPagenameAvailable(response.isAvailable);
})
- .catch(error => {
- console.log(error);
- // errorToast(error.message);
+ .catch(err => {
+ errorToast(err);
});
}, 300);
return () => {
@@ -87,16 +85,15 @@ const DialogNewPage = ({ isOpen, setIsOpen, addNewPage }: DialogNewPageProps) =>
};
}, [pagename]);
- const createNewPage = (data: CreatePageFormData) => {
+ const submitNewPage = (data: CreatePageFormData) => {
createPage(data.pagename)
.then(res => {
addNewPage(res.page);
- // successToast("Page successfully created.");
+ successToast("Page successfully created.");
setIsOpen(false);
})
- .catch(error => {
- console.log(error);
- // errorToast(error.message);
+ .catch(err => {
+ errorToast(err);
});
};
@@ -114,45 +111,44 @@ const DialogNewPage = ({ isOpen, setIsOpen, addNewPage }: DialogNewPageProps) =>
>
- To prevent a BOT rush to get all short size page names, you can only create pages with at least 4 characters.
- If you still wants a short page name, there's some ways to get one:
+ To prevent a rush of BOTs trying to claim all short-sized page names, you can only create pages with at least
+ 4 characters. However, if you still want a short page name, there are a few ways to obtain one:
- 1. Subscriptions. (SOON)
+ 1. Subscriptions. (COMING SOON)
- 2. Prove that you owns that pagename in some other social medias like Instagram, Twitter, TikTok, etc.
+ 2. Prove that you own that page name on other social media platforms such as Instagram, Twitter, TikTok, etc.
- 3. Future events on discord. (SOON)
+ 3. Future events on Discord. (COMING SOON)
- 4. Be a nicely and lovely person on our discord server maybe?! 😳
+ 4. Maybe be a nice and lovely person on our Discord server?! 😳
- You can also create up to 2 pages per account without any subscription.
+ Additionally, you can create up to 2 pages per account without any subscription.
-