Skip to content

Commit

Permalink
Merge pull request #18 from websitesieutoc/move-next-template-to-tubor
Browse files Browse the repository at this point in the history
Move next template to tubor
  • Loading branch information
sangdth authored Oct 21, 2023
2 parents 31075d0 + 701891d commit 5367be6
Show file tree
Hide file tree
Showing 182 changed files with 10,608 additions and 215 deletions.
22 changes: 22 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
ARGON_SECRET=topsecret
NEXTAUTH_SECRET=topsecret
NEXTAUTH_URL=http://localhost:3000

KV_REST_API_URL=
KV_REST_API_TOKEN=

# NOTE: The DB/USER/PASSWORD need to be the same with docker-compose values
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_URL_NON_POOLING="${POSTGRES_URL}"
POSTGRES_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?schema=public"
POSTGRES_PRISMA_URL="${POSTGRES_URL}?pgbouncer=true&connect_timeout=15"

NEXT_PUBLIC_API_KEY_TINYMCE=""

EMAIL_SERVER_USER= # your email
EMAIL_SERVER_PASSWORD= # your password
EMAIL_SERVER_HOST= #smtp.gmail.com
EMAIL_SERVER_PORT= #465
SENDGRID_API_KEY=
11 changes: 10 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,14 @@
{
"mode": "auto"
}
]
],
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/node_modules": false
}
}
13 changes: 13 additions & 0 deletions apps/dashboard/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
root: true,
extends: ['sieutoc'],
rules: {
'no-plusplus': 'off',
'no-underscore-dangle': 'off',
'react/display-name': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
36 changes: 36 additions & 0 deletions apps/dashboard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
19 changes: 19 additions & 0 deletions apps/dashboard/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "http://json.schemastore.org/prettierrc",
"overrides": [
{
"files": ["*.yaml", "*.json"],
"options": {
"singleQuote": false
}
},
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"options": {
"printWidth": 80,
"singleQuote": true,
"trailingComma": "es5"
}
}
]
}
82 changes: 82 additions & 0 deletions apps/dashboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Features

This template includes the following:

- Next.js 13
- TypeScript
- ESLint
- Prettier
- Chakra UI
- Prisma
- Next-Auth
- Docker Compose with:
- PostgresQL
- Redis
- Mailpit

## Demo

https://nextjs-template-demo.vercel.app


## Getting Started

#### For Development

- We use `pnpm` package manager. Get it [here](https://pnpm.io/installation).
- Make sure Docker up and running.
- If your Docker account has 2FA enabled, you have to create a Personal Access Token and login before:
- Follow [this guide](https://docs.docker.com/docker-hub/access-tokens/).
- Login with `docker login --username <your-username>`

#### Clone the project

You can either use this template by:

- Click the **"Use this template"** button and follow the instruction
- Or using the script below:

```bash
npx tiged websitesieutoc/nextjs-template your-project
```

Then, search and replace `nextjs-template` with your project slug.

#### Install dependencies

```bash
cd your-project
pnpm install
```

#### Setup environment variables

For the first time, you need some default environment variables:

```bash
cp .env.example .env
```

Then, run the development server:

```bash
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser and start developing.

## Good to know

- This project uses `App Router` feature.
- We try to take adventage of Next.js's ecosystem, thus most of the features here are built on top of Next.js best practices.
- We use Chakra UI as our primary library. For ready-made themes, please find it at our [themes](https://github.com/websitesieutoc/themes) repo.
- In the future we will launch a tool for customising your own themes soon!

#### Why do not use <headless-cms-name> here?

- We're fully aware of the headless CMS system.
- But there are ton of boilerplats out there, which already do a great jobs, so we do not want to re-invent them.
- Most of them are really tightly coupled with the headless CMS API, so customers always end up to hack around a lot.
- We need only a lite version of CMS, nothing else.
29 changes: 29 additions & 0 deletions apps/dashboard/app/[locale]/(auth)/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'; // Error components must be Client Components

import { Box, useToast } from '@/components/chakra';
import { ErrorBoundary } from '@/components/client';
import { useEffect } from 'react';

export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
const toast = useToast();
useEffect(() => {
// Log the error to an error reporting service
toast({
status: 'error',
title: error.name,
description: error.message,
});
}, [error, toast]);

return (
<Box textAlign="center">
<ErrorBoundary error={error} reset={reset} />
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client';

import { useRouter, useState } from '@/hooks';
import { Input, Button, Box, Code } from '@/components/chakra';
import { FormWrapper } from '@/components/client';
import { HttpMethod } from '@/types';
import { fetcher } from '@/utils/fetcher';

export const ForgotPasswordForm = () => {
const [error, setError] = useState('');
const router = useRouter();

const handleAction = async (formData: FormData) => {
setError('');
const { email } = Object.fromEntries(formData.entries()) as {
email: string;
};
if (!email || !email.trim()) return alert('Please enter email address!');

const request = await fetcher<{
data?: { email: string };
status: number;
message: string;
}>(`/api/users/password`, {
method: HttpMethod.POST,
body: JSON.stringify({
email: email,
}),
});

if (request?.data?.email) {
router.push(
`/forgot-password/update-password?email=${request.data.email}`
);
} else {
setError(request.message);
}
};

return (
<FormWrapper action={handleAction}>
<Input autoFocus name="email" type="email" isRequired />
{error && <Code color="red">{error}</Code>}
<Button width="100%" marginTop={3} type="submit" size="lg">
Continue
</Button>
</FormWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';

import { redirect } from 'next/navigation';
import { Box, Icon, Stack } from '@/components/chakra';
import { CheckCircleIcon, WarningTwoIcon } from '@/icons';
import { User } from '@/types';
import { useEffect, useRouter } from '@/hooks';

type StatusPropTypes = {
isTimeOver: boolean;
result: {
data?: Omit<User, 'password'>;
status: number;
description: string;
};
};
export const Status = ({ result, isTimeOver }: StatusPropTypes) => {
const router = useRouter();
useEffect(() => {
if (!result || isTimeOver) {
router.push('/forgot-password/confirm-email');
}
setTimeout(() => redirect('/'), 5_000);
}, [result, isTimeOver, router]);

return (
<Stack
color={result?.status === 200 ? 'green.400' : 'red.400'}
direction="row"
spacing={1}
>
<Icon
as={result?.status === 200 ? CheckCircleIcon : WarningTwoIcon}
boxSize={6}
/>
<Box>{result?.description}</Box>
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Container, Skeleton, Stack } from '@/components/chakra';

export default function ConfirmEmailLoading() {
return (
<Stack spacing={8} maxWidth="md" marginX="auto" paddingTop="10vh">
<Container shadow="base" rounded={10} paddingX={10} padding={10}>
<Stack padding={3} spacing={1} maxW={650} justify="center">
<Skeleton height="40px" rounded={5} width="150px" marginBottom={4} />

<Skeleton height="40px" rounded={5} />
<Skeleton height="40px" rounded={5} />
<Skeleton height="40px" rounded={5} />
<Skeleton height="40px" rounded={5} />
</Stack>
</Container>
</Stack>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { redirect } from 'next/navigation';
import { Stack } from '@/components/chakra';
import { Container } from '@/components/chakra';
import { Locale } from '@/types';
import { differenceInHours } from 'date-fns';
import { Status } from './Status';
import { verifyResetPassword } from '@/services/users';
import { HOUR_MAX_CONFIRM } from '@/utils/constants';

type ConfirmResetPassFromEmailType = {
params: { locale: Locale };
searchParams: { code: string };
};
export default async function ConfirmResetPassFromEmail(
props: ConfirmResetPassFromEmailType
) {
const { code } = props.searchParams;
const items = code?.split(':');

if (items?.length !== 4) {
redirect('/');
}

const dateTimeSubmit = Number(items?.[0]);
const confirmCode = items?.[0] + ':' + items?.[1];
const newPassword = items?.[2]?.replace(/ /g, '+');

const email = items?.[3];
const now = Date.now();
const timeDifference = differenceInHours(now, dateTimeSubmit);
const isTimeOver = timeDifference >= HOUR_MAX_CONFIRM;
const data = isTimeOver
? {
data: undefined,
status: 404,
description: 'Time limit expired',
}
: await verifyResetPassword(email, newPassword, confirmCode);

return (
<Stack spacing={8} maxWidth="md" marginX="auto" paddingTop="10vh">
<Container shadow="base" rounded={10} paddingX={10} padding={10}>
<Stack padding={3} spacing={1} maxW={650} justify="center">
<Status isTimeOver={isTimeOver} result={data} />
</Stack>
</Container>
</Stack>
);
}
Loading

0 comments on commit 5367be6

Please sign in to comment.