Skip to content

Commit

Permalink
Merge pull request #118 from edgedb/add-remix-auth-example
Browse files Browse the repository at this point in the history
Add remix auth example
  • Loading branch information
diksipav authored Jan 3, 2024
2 parents 7fe9ebe + 73ba9a0 commit 0ff9793
Show file tree
Hide file tree
Showing 37 changed files with 13,261 additions and 0 deletions.
4 changes: 4 additions & 0 deletions remix-auth/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
};
6 changes: 6 additions & 0 deletions remix-auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/node_modules

/.cache
/build
/public/build
.env
40 changes: 40 additions & 0 deletions remix-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Welcome to EdgeDB Auth todo example app built with Remix!

- [EdgeDB Auth](https://www.edgedb.com/docs/guides/auth/index)
- [EdgeDB Docs](https://www.edgedb.com/docs)
- [Remix Docs](https://remix.run/docs)

## Development

From your terminal:

```sh
npm run dev
```

This starts your app in development mode, rebuilding assets on file changes.

## Deployment

First, build your app for production:

```sh
npm run build
```

Then run the app in production mode:

```sh
npm start
```

Now you'll need to pick a host to deploy it to.

### DIY

If you're familiar with deploying node applications, the built-in Remix app server is production-ready.

Make sure to deploy the output of `remix build`

- `build/`
- `public/build/`
42 changes: 42 additions & 0 deletions remix-auth/app/components/auth/ForgotPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Form } from "@remix-run/react";
import SubmitButton from "./SubmitButton";

interface ResetPasswordFormProps {
error?: string | null;
message?: string | null;
}

export default function ForgotPasswordForm({
error,
message,
}: ResetPasswordFormProps) {
return (
<Form className="flex flex-col w-[22rem]" method="post">
{error ? (
<div className="bg-rose-100 text-rose-950 px-4 py-3 rounded-md mb-3">
{error}
</div>
) : null}

{message ? (
<div className="bg-sky-200 text-sky-950 px-4 py-3 rounded-md mb-3">
{message}
</div>
) : (
<>
<label htmlFor="email" className="font-medium text-sm mb-1 ml-2">
Email
</label>
<input
type="email"
id="email"
name="email"
required
className="bg-slate-50 border border-slate-200 rounded-lg mb-4 px-4 py-3 outline-teal-600 outline-2 focus:outline focus:bg-white"
/>
<SubmitButton label="Send reset email" />
</>
)}
</Form>
);
}
39 changes: 39 additions & 0 deletions remix-auth/app/components/auth/ResendVerificationEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
interface ResendVerificationEmailProps {
verificationToken: string;
error?: string | null;
message?: string | null;
}

export default function ResendVerificationEmail({
verificationToken,
error,
message,
}: ResendVerificationEmailProps) {
return (
<form method="post">
{error || message ? (
<div
className={`${
error ? "bg-rose-100 text-rose-950" : "bg-sky-200 text-sky-950"
} px-4 py-3 rounded-md mb-3`}
>
{error || message}
</div>
) : null}

<input
type="hidden"
name="verification_token"
defaultValue={verificationToken}
/>
<button
name="action"
value="resendVerEmail"
type="submit"
className="text-sky-600 mt-2"
>
Resend verification email
</button>
</form>
);
}
36 changes: 36 additions & 0 deletions remix-auth/app/components/auth/ResetPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import SubmitButton from "./SubmitButton";

interface ResetPasswordFormProps {
error?: string | null;
resetToken: string;
}

export default function ResetPasswordForm({
error,
resetToken,
}: ResetPasswordFormProps) {
return (
<form method="post" className="flex flex-col w-[22rem]">
{error ? (
<div className="bg-rose-100 text-rose-950 px-4 py-3 rounded-md mb-3">
{error}
</div>
) : (
<>
<label htmlFor="password" className="font-medium text-sm mb-1 ml-2">
New password
</label>
<input
type="password"
id="password"
name="password"
required
className="bg-slate-50 border border-slate-200 rounded-lg mb-4 px-4 py-3 outline-sky-500 outline-2 focus:outline focus:bg-white"
/>
<input type="hidden" name="reset_token" defaultValue={resetToken} />
<SubmitButton label="Set new password" />
</>
)}
</form>
);
}
54 changes: 54 additions & 0 deletions remix-auth/app/components/auth/SigninForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Form, Link } from "@remix-run/react";
import SubmitButton from "./SubmitButton";

interface SigninFormProps {
error?: string | null;
}

export default function SigninForm({ error }: SigninFormProps) {
return (
<Form className="flex flex-col w-[22rem]" method="post">
{error ? (
<div className="bg-rose-100 text-rose-950 px-4 py-3 rounded-md mb-3">
{error}
</div>
) : null}
<label htmlFor="email" className="font-medium text-sm mb-1 ml-2">
Email
</label>
<input
type="email"
id="email"
name="email"
required
className="bg-slate-50 border border-slate-200 rounded-lg mb-4 px-4 py-3 outline-teal-600 outline-2 focus:outline focus:bg-white"
/>
<div className="flex text-sm">
<label htmlFor="password" className="font-medium mb-1 ml-2">
Password
</label>
<Link
to="/forgot-password"
className="ml-auto text-teal-600 hover:text-teal-700"
>
Forgot password?
</Link>
</div>
<input
type="password"
id="password"
name="password"
required
className="bg-slate-50 border border-slate-200 rounded-lg mb-4 px-4 py-3 outline-teal-600 outline-2 focus:outline focus:bg-white"
/>
<SubmitButton label="Sign in" />

<div className="text-slate-500 mt-3">
Don't have an account?{" "}
<Link to="/signup" className="text-teal-600 hover:text-teal-700">
Sign up
</Link>
</div>
</Form>
);
}
52 changes: 52 additions & 0 deletions remix-auth/app/components/auth/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Form, Link } from "@remix-run/react";
import SubmitButton from "./SubmitButton";

interface SignupFormProps {
error?: string | null;
message?: string | null;
}

export default function SignupForm({ error, message }: SignupFormProps) {
return (
<Form className="flex flex-col w-[22rem]" method="post">
{error || message ? (
<div
className={`${
error ? "bg-rose-100 text-rose-950" : "bg-sky-200 text-sky-950"
} px-4 py-3 rounded-md mb-3`}
>
{error || message}
</div>
) : null}

<label htmlFor="email" className="font-medium text-sm mb-1 ml-2">
Email
</label>
<input
type="email"
id="email"
name="email"
required
className="bg-slate-50 border border-slate-200 rounded-lg mb-4 px-4 py-3 outline-teal-600 outline-2 focus:outline focus:bg-white"
/>
<label htmlFor="password" className="font-medium text-sm mb-1 ml-2">
Password
</label>
<input
type="password"
id="password"
name="password"
required
className="bg-slate-50 border border-slate-200 rounded-lg mb-4 px-4 py-3 outline-teal-600 outline-2 focus:outline focus:bg-white"
/>
<SubmitButton label="Sign up" />

<div className="text-slate-500 mt-3">
Already have an account?{" "}
<Link to="/signin" className="text-teal-600 hover:text-teal-700">
Sign in
</Link>
</div>
</Form>
);
}
10 changes: 10 additions & 0 deletions remix-auth/app/components/auth/SubmitButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function SubmitButton({ label }: { label: string }) {
return (
<button
type="submit"
className={`bg-teal-600 text-white p-3 rounded-md mt-3 shadow-md hover:scale-[1.03] transition-transform`}
>
{label}
</button>
);
}
53 changes: 53 additions & 0 deletions remix-auth/app/components/todos/TodoCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Form } from "@remix-run/react";
import { CheckIcon, DeleteIcon } from "~/icons";

export interface Todo {
id: string;
content: string;
completed: boolean;
created_on: string;
}

export function TodoCard({ id, content, completed, created_on }: Todo) {
return (
<div className="bg-white shadow-sm rounded-xl p-4 text-lg flex hover:scale-[1.02] transition-transform duration-75">
<Form method="put">
<input
type="hidden"
name="todo"
defaultValue={JSON.stringify({ id, content, completed, created_on })}
/>
<button
type="submit"
className={`w-9 h-9 rounded-full mr-4 flex-shrink-0 flex items-center justify-center cursor-pointer ${
completed ? "bg-sky-400 text-white" : "border border-slate-400"
}`}
>
{completed ? <CheckIcon /> : null}
</button>
</Form>

<span
className={`flex-shrink overflow-hidden break-words py-1 mr-auto ${
completed ? "line-through opacity-70" : ""
}`}
>
{content}
</span>

<Form method="delete">
<input
type="hidden"
name="todo"
defaultValue={JSON.stringify({ id, content, completed, created_on })}
/>
<button
type="submit"
className="w-9 h-9 rounded-full ml-4 flex-shrink-0 flex items-center justify-center cursor-pointer text-slate-400 hover:bg-rose-500 hover:text-white"
>
<DeleteIcon />
</button>
</Form>
</div>
);
}
52 changes: 52 additions & 0 deletions remix-auth/app/components/todos/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { SendIcon } from "~/icons";
import { Form, useNavigation } from "@remix-run/react";
import { type Todo, TodoCard } from "./TodoCard";
import { useEffect, useRef } from "react";

export function TodoList({ todos }: { todos: Todo[] | null }) {
const navigation = useNavigation();

const isSubmitting = navigation.state === "submitting";

const formRef = useRef<HTMLFormElement>(null);
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
if (isSubmitting) return;
formRef.current?.reset();
inputRef.current?.focus();
}, [isSubmitting]);

return (
<div className="flex flex-col gap-3 mt-4">
<Form
ref={formRef}
method="post"
className="flex bg-slate-50 border border-slate-200 rounded-xl mb-4 overflow-hidden outline-teal-600 outline-[3px] focus-within:outline focus-within:bg-white"
>
<input
ref={inputRef}
type="text"
name="newTodo"
placeholder="Add new todo..."
className="flex-grow bg-transparent pl-5 text-lg focus:outline-none focus-visible:ring-0"
/>
<button
type="submit"
className="h-9 w-9 p-3 box-content flex items-center justify-center text-teal-600"
tabIndex={-1}
>
<SendIcon />
</button>
</Form>

{todos?.length ? (
todos.map((todo) => <TodoCard key={todo.id} {...todo} />)
) : (
<div className="border-dashed border-2 border-slate-300 text-slate-500 h-8 py-4 px-8 box-content flex items-center rounded-xl">
You have no todo's
</div>
)}
</div>
);
}
Loading

0 comments on commit 0ff9793

Please sign in to comment.