Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 0 additions & 3 deletions .eslintrc.json

This file was deleted.

2 changes: 1 addition & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"labels": ["dependencies"]
}
}
12 changes: 6 additions & 6 deletions .github/workflows/next-build.yml → .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '#skip-lint')"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: "16"
node-version: "20"
cache: yarn
- name: Install dependencies
run: yarn install
Expand All @@ -26,11 +26,11 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '#skip-build')"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: "16"
node-version: "20"
cache: yarn
- name: Install dependencies
run: yarn install
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ yarn-error.log*

# typescript
*.tsbuildinfo

.dev.vars
.wrangler
.react-router
85 changes: 72 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,79 @@
# DropBox-Index
# Welcome to React Router!

Index your dropbox files publically. Built with NextJS & Tailwind
A modern, production-ready template for building full-stack React applications using React Router.

[![NextJS Build Test](https://github.com/ArnabXD/Dropbox-Index/actions/workflows/next-build.yml/badge.svg)](https://github.com/ArnabXD/Dropbox-Index/actions/workflows/next-build.yml)
[![Total alerts](https://img.shields.io/lgtm/alerts/g/ArnabXD/Dropbox-Index.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ArnabXD/Dropbox-Index/alerts/)
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/ArnabXD/Dropbox-Index.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ArnabXD/Dropbox-Index/context:javascript)
## Features

## ENV Vars
- 🚀 Server-side rendering
- ⚡️ Hot Module Replacement (HMR)
- 📦 Asset bundling and optimization
- 🔄 Data loading and mutations
- 🔒 TypeScript by default
- 🎉 TailwindCSS for styling
- 📖 [React Router docs](https://reactrouter.com/)

- `APP_ID` : Dropbox App Client ID
- `APP_SECRET` : Dropbox App Client Secret
- `REFRESH_TOKEN` : Dropbox refresh token
- `NEXT_PUBLIC_TITLE` : Website title and Navbar heading
## Getting Started

## Deploy
### Installation

[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy)
Install the dependencies:

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FArnabXD%2FDropbox-Index&env=APP_ID,APP_SECRET,REFRESH_TOKEN,NEXT_PUBLIC_TITLE)
```bash
npm install
```

### Development

Start the development server with HMR:

```bash
npm run dev
```

Your application will be available at `http://localhost:5173`.

## Previewing the Production Build

Preview the production build locally:

```bash
npm run preview
```

## Building for Production

Create a production build:

```bash
npm run build
```

## Deployment

Deployment is done using the Wrangler CLI.

To build and deploy directly to production:

```sh
npm run deploy
```

To deploy a preview URL:

```sh
npx wrangler versions upload
```

You can then promote a version to production after verification or roll it out progressively.

```sh
npx wrangler versions deploy
```

## Styling

This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.

---

Built with ❤️ using React Router.
11 changes: 11 additions & 0 deletions app/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@import "tailwindcss";

html,
body {
@apply bg-white dark:bg-slate-950;
@apply text-slate-950 dark:text-white;

@media (prefers-color-scheme: dark) {
color-scheme: dark;
}
}
44 changes: 44 additions & 0 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { AppLoadContext, EntryContext } from "react-router";
import { ServerRouter } from "react-router";
import { isbot } from "isbot";
import { renderToReadableStream } from "react-dom/server";

export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
_loadContext: AppLoadContext,
) {
let shellRendered = false;
let status = responseStatusCode;
const userAgent = request.headers.get("user-agent");

const body = await renderToReadableStream(
<ServerRouter context={routerContext} url={request.url} />,
{
onError(error: unknown) {
status = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
},
);
shellRendered = true;

// Ensure requests from bots and SPA Mode renders wait for all content to load before responding
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
await body.allReady;
}

responseHeaders.set("Content-Type", "text/html");
return new Response(body, {
headers: responseHeaders,
status,
});
}
75 changes: 75 additions & 0 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";

import type { Route } from "./+types/root";
import "./app.css";

export const links: Route.LinksFunction = () => [
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossOrigin: "anonymous",
},
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
},
];

export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

export default function App() {
return <Outlet />;
}

export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;

if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error";
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}

return (
<main className="pt-16 p-4 container mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
}
7 changes: 7 additions & 0 deletions app/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type RouteConfig, route, index } from "@react-router/dev/routes";

export default [
index("routes/home.tsx"),
route("/setup", "routes/setup.tsx"),
route("/authorize", "routes/authorize.tsx"),
] satisfies RouteConfig;
59 changes: 59 additions & 0 deletions app/routes/authorize.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { redirect } from "react-router";

import type { Route } from "./+types/authorize";
import { DropboxService } from "~/services/dropbox";

export const meta = ({ data }: Route.MetaArgs) => {
return [
{ title: `${data.title} | ${data.error}` },
{ name: "description", content: data.error_description },
];
};

export const loader = async ({ context, request }: Route.LoaderArgs) => {
const dropbox = new DropboxService(
context.cloudflare.env.DROPBOX_APP_KEY,
context.cloudflare.env.DROPBOX_APP_SECRET,
context.cloudflare.env.TOKEN,
);

const code = new URL(request.url).searchParams.get("code");
if (!code) {
return {
error: "Missing code",
error_description: "The authorization code is missing.",
title: context.cloudflare.env.VITE_SITE_TITLE,
};
}

const token = await dropbox.getToken(code, request.url);

if ("access_token" in token) {
return redirect("/home");
}

return {
error: token.error,
error_description: token.error_description,
title: context.cloudflare.env.VITE_SITE_TITLE,
};
};

export default function Authorize({ loaderData }: Route.ComponentProps) {
if (!loaderData.error) {
return (
<div className="flex flex-col items-center justify-center h-screen w-screen">
<h1 className="text-2xl text-green-500">Success</h1>
<p>Authorization successful!</p>
</div>
);
}

return (
<div className="flex flex-col items-center justify-center h-screen w-screen">
<h1 className="text-2xl text-red-500">Error</h1>
<p>{loaderData.error}</p>
<p>{loaderData.error_description}</p>
</div>
);
}
18 changes: 18 additions & 0 deletions app/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Route } from "./+types/home";

export function meta({ data }: Route.MetaArgs) {
return [
{ title: data.title },
{ name: "description", content: "Welcome to React Router!" },
];
}

export function loader({ context }: Route.LoaderArgs) {
return {
title: context.cloudflare.env.VITE_SITE_TITLE,
};
}

export default function Home({ loaderData }: Route.ComponentProps) {
return <h1>Hi</h1>;
}
Loading
Loading