Skip to content

Commit

Permalink
feat(example-umami): implement account pages
Browse files Browse the repository at this point in the history
  • Loading branch information
shadcn committed Jun 2, 2022
1 parent 4231e2c commit 933c14e
Show file tree
Hide file tree
Showing 28 changed files with 512 additions and 43 deletions.
2 changes: 1 addition & 1 deletion examples/example-umami/components/breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function Breadcrumbs({ items, ...props }: BreadcrumbsProps) {
<nav className="py-4 text-text" {...props}>
<ol className="flex">
{items.map((item, index) => (
<li key={index} className="flex items-center leading-none">
<li key={index} className="flex items-center leading-none truncate">
{item.url ? (
<Link href={item.url} passHref>
<a className="underline text-link">{item.title}</a>
Expand Down
7 changes: 3 additions & 4 deletions examples/example-umami/components/form--article.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react"
import classNames from "classnames"
import { useTranslation } from "next-i18next"
import { useRouter } from "next/router"

interface FormArticleProps extends React.HTMLProps<HTMLFormElement> {}

Expand All @@ -12,6 +13,7 @@ interface FormStatus {
export function FormArticle({ className, ...props }: FormArticleProps) {
const [formStatus, setFormStatus] = React.useState<FormStatus>(null)
const { t } = useTranslation()
const router = useRouter()

const onSubmit = async (event) => {
event.preventDefault()
Expand All @@ -33,10 +35,7 @@ export function FormArticle({ className, ...props }: FormArticleProps) {
})
}

return setFormStatus({
status: "success",
message: t("your-message-has-been-sent"),
})
router.push("/account")
}

return (
Expand Down
104 changes: 104 additions & 0 deletions examples/example-umami/components/form--login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as React from "react"
import classNames from "classnames"
import { useTranslation } from "next-i18next"
import { signIn } from "next-auth/react"
import { useRouter } from "next/router"

interface FormLoginProps extends React.HTMLProps<HTMLFormElement> {}

interface FormStatus {
status: "success" | "error" | "fetching"
message?: string
}

export function FormLogin({ className, ...props }: FormLoginProps) {
const [formStatus, setFormStatus] = React.useState<FormStatus>(null)
const { t } = useTranslation()
const router = useRouter()

React.useEffect(() => {
if (router.query.error === "CredentialsSignin") {
return setFormStatus({
status: "error",
message: t("unrecognized-username-or-password-please-try-again"),
})
}

return setFormStatus(null)
}, [router, t])

const onSubmit = async (event) => {
event.preventDefault()
const data = new FormData(event.target)

setFormStatus({ status: "fetching" })

await signIn("credentials", {
username: data.get("username"),
password: data.get("password"),
})

return setFormStatus({
status: "success",
})
}

return (
<form
className={classNames("grid gap-4", className)}
onSubmit={onSubmit}
{...props}
>
{formStatus?.message && (
<p
className={classNames("py-3 px-4 border", {
"border-link bg-link/10 text-link":
formStatus?.status === "success",
"border-error bg-error/10 text-error":
formStatus?.status === "error",
})}
>
{formStatus.message}
</p>
)}
<div className="grid gap-2">
<label htmlFor="username" className="font-semibold text-text">
{t("username")} <span className="text-sm text-red-500">*</span>
</label>
<input
id="username"
name="username"
maxLength={255}
required
className="px-2 py-3 border-2 border-gray focus:outline-dotted focus:outline-offset-2 focus:ring-0 focus:outline-link focus:border-gray"
/>
<p className="text-sm text-text">{t("enter-your-drupal-username")}</p>
</div>
<div className="grid gap-2">
<label htmlFor="password" className="font-semibold text-text">
{t("password")} <span className="text-sm text-red-500">*</span>
</label>
<input
type="password"
id="password"
name="password"
required
className="px-2 py-3 border-2 border-gray focus:outline-dotted focus:outline-offset-2 focus:ring-0 focus:outline-link focus:border-gray"
/>
<p className="text-sm text-text">
{t("enter-the-password-that-accompanies-your-username")}
</p>
</div>
<div>
<input
type="submit"
className="px-6 py-3 font-serif text-xl text-white transition-colors border-2 rounded-sm cursor-pointer bg-link hover:bg-white hover:text-black border-link"
disabled={formStatus?.status === "fetching"}
value={
formStatus?.status === "fetching" ? t("please-wait") : t("login")
}
/>
</div>
</form>
)
}
4 changes: 2 additions & 2 deletions examples/example-umami/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ export function Header({ menus }: HeaderProps) {
</div>
<div className="container relative flex-wrap items-center justify-between py-6 md:flex lg:py-10">
<Link href="/" passHref>
<a className="flex justify-start w-52">
<a className="flex justify-start w-48 h-12 lg:h-16 lg:w-52">
<Logo className="text-primary" />
<span className="sr-only">{siteConfig.name}</span>
</a>
</Link>
<button
className="absolute transition-all border beorder-transparent md:hidden right-4 top-10 hover:border-link"
className="absolute transition-all border beorder-transparent md:hidden right-4 top-8 hover:border-link"
onClick={() => setShowMenu(!showMenu)}
>
<svg
Expand Down
23 changes: 23 additions & 0 deletions examples/example-umami/components/menu-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from "react"
import NextLink, { LinkProps as NextLinkProps } from "next/link"

export type MenuLinkProps = Omit<
NextLinkProps,
"as" | "passHref" | "children"
> &
React.HTMLAttributes<HTMLAnchorElement>

function CustomLink(props, ref) {
let { href, children, ...rest } = props
return (
<NextLink href={href} passHref>
<a ref={ref} {...rest}>
{children}
</a>
</NextLink>
)
}

export const MenuLink = React.forwardRef<HTMLAnchorElement, MenuLinkProps>(
CustomLink
)
64 changes: 54 additions & 10 deletions examples/example-umami/components/menu-user.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Link from "next/link"
import { useTranslation } from "next-i18next"
import { useSession } from "next-auth/react"
import { useSession, signOut } from "next-auth/react"
import { Menu, Transition } from "@headlessui/react"
import classNames from "classnames"

import { MenuLink } from "components/menu-link"

export function MenuUser() {
const { data, status } = useSession()
Expand All @@ -12,22 +16,62 @@ export function MenuUser() {

if (status === "unauthenticated") {
return (
<Link href="/api/auth/signin" passHref>
<a className="text-text">{t("login")}</a>
<Link href="/login" passHref>
<a className="text-text hover:underline">{t("login")}</a>
</Link>
)
}

if (status === "authenticated") {
return (
<div className="flex space-x-4">
<p>
{t("welcome-back")}{" "}
<span className="font-semibold">{data.user.name}</span>
</p>
<Link href="/api/auth/signout" passHref>
<a className="text-text">{t("Log out")}</a>
</Link>
<Menu as="div" className="relative z-50">
<Menu.Button className="focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-opacity-75">
{t("my-account")}
</Menu.Button>
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Menu.Items className="absolute right-0 w-48 mt-2 origin-top-right bg-white border divide-y shadow-md border-border divide-border">
<Menu.Item as="div" className="flex flex-col px-3 py-2">
<p className="font-semibold">{data.user.name}</p>
<p className="text-sm truncate text-gray">{data.user.email}</p>
</Menu.Item>
<Menu.Item>
{({ active }) => (
<MenuLink
href="/account"
className={classNames(
"flex hover:bg-body w-full px-3 py-2 text-text",
{
"bg-body": active,
}
)}
>
{t("my-account")}
</MenuLink>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
className={classNames("flex w-full px-3 py-2 text-text", {
"bg-body": active,
})}
onClick={() => signOut()}
>
{t("logout")}
</button>
)}
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/example-umami/components/meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function Meta({ title, description }: MetaProps) {
<title>
{title} | {siteConfig.name}
</title>
{description && <meta name="description" content={description} />}
<meta name="description" content={description || siteConfig.slogan} />
<meta
property="og:image"
content={`${process.env.NEXT_PUBLIC_BASE_URL}/images/meta.jpg`}
Expand Down
53 changes: 53 additions & 0 deletions examples/example-umami/components/node--article--row.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { DrupalNode } from "next-drupal"
import { useTranslation } from "next-i18next"
import { useRouter } from "next/router"

import { formatDate } from "lib/utils"
import { MediaImage } from "components/media--image"

interface NodeArticleRowProps {
node: DrupalNode
}

export function NodeArticleRow({ node, ...props }: NodeArticleRowProps) {
const { t } = useTranslation()
const router = useRouter()

async function handleDelete() {
if (!window?.confirm(t("are-you-use-you-want-to-delete-this-article"))) {
return
}

const response = await fetch(`/api/articles/${node.id}`, {
method: "DELETE",
})

if (response?.ok) {
router.reload()
}
}

return (
<article
className="relative grid grid-cols-[120px_1fr] items-start gap-4 p-4 overflow-hidden bg-white border border-border group"
{...props}
>
<MediaImage media={node.field_media_image} width={115} height={75} />
<div className="flex items-start justify-between text-text">
<div>
<h2 className="flex-1 font-serif text-xl">{node.title}</h2>
<p className="text-sm text-gray">
{formatDate(node.created)} -{" "}
{node.status ? t("published") : t("draft")}
</p>
</div>
<button
onClick={() => handleDelete()}
className="px-2 py-1 text-white rounded-md hover:bg-error bg-error/80"
>
{t("delete")}
</button>
</div>
</article>
)
}
2 changes: 1 addition & 1 deletion examples/example-umami/components/node--article.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function NodeArticle({ node, additionalContent }: NodeArticleProps) {
]}
/>
<article className="grid gap-8 pb-12 lg:grid-cols-10">
<div className="p-10 bg-white border border-border lg:col-span-7 text-text">
<div className="p-6 bg-white border md:p-10 border-border lg:col-span-7 text-text">
<h1 className="font-serif text-4xl">{node.title}</h1>
<div className="flex items-center my-4 space-x-2 text-sm">
{node.uid?.display_name ? (
Expand Down
8 changes: 4 additions & 4 deletions examples/example-umami/components/node--recipe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function NodeRecipe({ node, ...props }: NodeRecipeProps) {
},
]}
/>
<article className="bg-white border border-border p-9 text-text">
<article className="p-6 bg-white border border-border sm:p-9 text-text">
<div className="flex flex-col space-y-4">
<h1 className="font-serif text-4xl">{node.title}</h1>
{node.field_recipe_category?.length ? (
Expand Down Expand Up @@ -60,7 +60,7 @@ export function NodeRecipe({ node, ...props }: NodeRecipeProps) {
)}
</div>
<div className="grid gap-4 py-10 lg:grid-cols-2">
<MediaImage media={node.field_media_image} width={545} height={360} />
<MediaImage media={node.field_media_image} width={770} height={512} />
<div className="grid gap-4 lg:grid-cols-2">
{node.field_preparation_time && (
<div className="flex items-center space-x-2 lg:flex-col">
Expand Down Expand Up @@ -143,9 +143,9 @@ export function NodeRecipe({ node, ...props }: NodeRecipeProps) {
</div>
<div className="grid gap-8 md:grid-cols-3">
<div className="flex flex-col p-8 space-y-6 bg-body">
<h3 className="pb-3 font-serif text-2xl border-b lg:text-3xl border-pink">
<h2 className="pb-3 font-serif text-2xl border-b lg:text-3xl border-pink">
{t("ingredients")}
</h3>
</h2>
<ul className="divide-y divide-pink">
{node.field_ingredients?.map((ingredients, index) => (
<li key={index} className="py-2">
Expand Down
Loading

0 comments on commit 933c14e

Please sign in to comment.