Skip to content

Commit

Permalink
Merge pull request #64 from nghiapham1026/main
Browse files Browse the repository at this point in the history
  • Loading branch information
faisalsayed10 committed Aug 19, 2023
2 parents 5e25ec3 + 16e5221 commit 7452e88
Show file tree
Hide file tree
Showing 15 changed files with 686 additions and 23 deletions.
4 changes: 2 additions & 2 deletions apps/landing/components/Providers.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const PROVIDERS = [
{ name: "Firebase", src: "/logos/firebase.png", comingSoon: false },
{ name: "AWS", src: "/logos/amazonaws.png", comingSoon: false },
{ name: "Digital Ocean", src: "/logos/digitalocean.png", comingSoon: true },
{ name: "Digital Ocean", src: "/logos/digitalocean.png", comingSoon: false },
{ name: "Backblaze", src: "/logos/backblaze.png", comingSoon: false },
{ name: "Wasabi", src: "/logos/wasabi.png", comingSoon: false },
{ name: "Scaleway", src: "/logos/scaleway.png", comingSoon: true },
// { name: "Cloudflare", src: "/logos/cloudflare.png", comingSoon: true },
{ name: "Cloudinary", src: "/logos/cloudinary.png", comingSoon: true },
// { name: "Wasabi", src: "/logos/wasabi.png", comingSoon: true },
{ name: "Azure", src: "/logos/azure.png", comingSoon: true },
{ name: "Supabase", src: "/logos/supabase.png", comingSoon: true },
// { name: "Linode", src: "/logos/linode.png", comingSoon: true },
Expand Down
43 changes: 43 additions & 0 deletions apps/web/components/ui/WasabiRegionSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Select } from "@chakra-ui/react";
import React from "react";

const regions = [
{ name: "US East 1 (N. Virginia)", code: "us-east-1" },
{ name: "US East 2 (N. Virginia)", code: "us-east-2" },
{ name: "US Central 1 (Texas)", code: "us-central-1" },
{ name: "US West 1 (Oregon)", code: "us-west-1" },
{ name: "CA Central 1 (Toronto)", code: "ca-central-1" },
{ name: "EU Central 1 (Amsterdam)", code: "eu-central-1" },
{ name: "EU Central 2 (Frankfurt)", code: "eu-central-2" },
{ name: "EU West 1 (London)", code: "eu-west-1" },
{ name: "EU West 2 (Paris)", code: "eu-west-2" },
{ name: "AP Northeast 1 (Tokyo)", code: "ap-northeast-1" },
{ name: "AP Northeast 2 (Osaka)", code: "ap-Northeast-2" },
{ name: "AP Southeast 1 (Singapore)", code: "ap-southeast-1" },
{ name: "AP Southeast 2 (Sydney)", code: "ap-southeast-2" },
];

type Props = {
value: string;
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
};

const WasabiRegionSelect: React.FC<Props> = ({ value, onChange }) => {
return (
<Select
placeholder="Select Region"
variant="flushed"
value={value}
onChange={onChange}
isRequired
>
{regions.map((region) => (
<option key={region.code} value={region.code}>
{region.name} - {region.code}
</option>
))}
</Select>
);
};

export default WasabiRegionSelect;
26 changes: 14 additions & 12 deletions apps/web/hooks/useBucket.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,18 @@ export const ROOT_FOLDER: DriveFolder = {

export default function useBucket(): ContextValue {
const { keys } = useKeys();

if ((Provider[keys.type] as Provider) === Provider.firebase) {
return useFirebase();
} else if ((Provider[keys.type] as Provider) === Provider.s3) {
return useS3();
} else if ((Provider[keys.type] as Provider) === Provider.backblaze) {
return useS3();
} else if ((Provider[keys.type] as Provider) === Provider.cloudflare) {
return useS3();
}

return null;

switch (Provider[keys.type] as Provider) {
case Provider.firebase:
return useFirebase();
case Provider.s3:
case Provider.backblaze:
case Provider.wasabi:
case Provider.digitalocean:
case Provider.cloudflare:
return useS3();
default:
return null;
}
}

15 changes: 10 additions & 5 deletions apps/web/hooks/useS3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,16 @@ export const S3Provider: React.FC<PropsWithChildren<Props>> = ({
isMounted.current = true;
if (data.keys.bucketUrl) return;

if ((Provider[data.type] as Provider) === Provider.s3) {
data.keys.bucketUrl = `https://${data.keys.Bucket}.s3.${data.keys.region}.amazonaws.com`;
} else if ((Provider[data.type] as Provider) === Provider.backblaze) {
data.keys.bucketUrl = `https://${data.keys.Bucket}.s3.${data.keys.region}.backblazeb2.com`;
}
switch (Provider[data.type] as Provider) {
case Provider.s3:
data.keys.bucketUrl = `https://${data.keys.Bucket}.s3.${data.keys.region}.amazonaws.com`;
break;
case Provider.backblaze:
data.keys.bucketUrl = `https://${data.keys.Bucket}.s3.${data.keys.region}.backblazeb2.com`;
break;
default:
break;
}

return () => {
isMounted.current = false;
Expand Down
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@chakra-ui/react": "^1.8.8",
"@emotion/react": "^11",
"@emotion/styled": "^11",
"@firebase/storage": "^0.11.2",
"@prisma/client": "4.12.0",
"@react-hook/media-query": "^1.1.1",
"@sendgrid/mail": "^7.7.0",
Expand Down Expand Up @@ -47,6 +48,7 @@
"react-hot-toast": "^2.2.0",
"react-loading-overlay": "^1.0.1",
"resend": "^0.17.1",
"sendgrid": "^5.2.3",
"swr": "^1.1.2-beta.0",
"tabler-icons-react": "^1.45.0",
"underscore": "^1.13.2",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/pages/api/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default withIronSessionApiRoute(async (req: NextApiRequest, res: NextApiR
try {
const token = await jwt.sign({ email }, process.env.JWT_SECRET, { expiresIn: "1h" });

if (process.env.RESEND_API_KEY) {
if (process.env.RESEND_API_KEY !== "undefined") {
await resend.emails.send({
from: process.env.EMAIL_FROM,
to: email,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/pages/drives/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const DrivePage: React.FC<Props> = ({ data }) => {
<FirebaseProvider data={data} fullPath={decodeURIComponent(folderPath)}>
<Dashboard />
</FirebaseProvider>
) : data.type === "s3" || data.type === "backblaze" || data.type === "cloudflare" ? (
) : data.type === "s3" || data.type === "backblaze" || data.type === "cloudflare" || data.type === "wasabi" || data.type === "digitalocean" || data.type === "cloudflare" ? (
<S3Provider data={data} fullPath={decodeURIComponent(folderPath)}>
<Dashboard />
</S3Provider>
Expand Down
206 changes: 206 additions & 0 deletions apps/web/pages/new/digitalocean.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { Bucket, ListBucketsCommandOutput } from "@aws-sdk/client-s3";
import {
Box,
Button,
Container,
Divider,
Flex,
Heading,
IconButton,
Input,
Select,
Text,
} from "@chakra-ui/react";
import VideoModal from "@components/ui/VideoModal";
import useUser from "@hooks/useUser";
import axios from "axios";
import Head from "next/head";
import { useRouter } from "next/router";
import { useState } from "react";
import toast from "react-hot-toast";
import { ArrowNarrowLeft } from "tabler-icons-react";
import "video-react/dist/video-react.css";

const NewS3 = () => {
const { user } = useUser();
const router = useRouter();
const [loading, setLoading] = useState(false);
const [keyId, setKeyId] = useState("");
const [applicationKey, setApplicationKey] = useState("");
const [endpoint, setEndpoint] = useState("");
const [bucketName, setBucketName] = useState("");
const [buckets, setBuckets] = useState<Bucket[]>([]);
const [selectedBucket, setSelectedBucket] = useState("Not Selected");

const listBuckets = async (e: React.FormEvent<HTMLDivElement>) => {
e.preventDefault();
setLoading(true);

try {
if (!user?.email) throw new Error("You need to login to perform this action!");

if (!keyId.trim() || !applicationKey.trim() || !endpoint.trim())
throw new Error("One or more fields are missing!");

const { data } = await axios.post<ListBucketsCommandOutput>("/api/s3/list-buckets", {
accessKey: keyId,
secretKey: applicationKey,
endpoint,
region: endpoint.split(".")[1],
});

setBuckets(data.Buckets);
} catch (err) {
console.error(err);
toast.error(err?.response?.data?.error || err.message);
}

setLoading(false);
};

const createBucket = async () => {
setLoading(true);

try {
if (!user?.email) throw new Error("You need to login to perform this action!");

if (!keyId.trim() || !applicationKey.trim())
throw new Error("One or more fields are missing!");

if ((selectedBucket === "Not Selected" && !bucketName.trim()) || !endpoint.trim())
throw new Error("Select an existing bucket or enter a new bucket name!");

if (
(selectedBucket === "Not Selected" && bucketName.trim().length < 3) ||
bucketName.trim().length > 63
)
throw new Error("Bucket name must be between 3 and 63 characters!");

const Bucket = selectedBucket !== "Not Selected" ? selectedBucket : bucketName.trim();

await axios.post("/api/drive", {
data: {
accessKey: keyId,
secretKey: applicationKey,
Bucket,
bucketUrl: `https://${Bucket}.${endpoint.split(".")[1]}.digitaloceanspaces.com`,
endpoint,
region: endpoint.split(".")[1],
},
name: Bucket,
type: "digitalocean",
});

toast.success("Drive created successfully!");
router.push("/");
} catch (err) {
console.error(err);
toast.error(err?.response?.data?.error || err.message);
}

setLoading(false);
};

return (
<>
<Head>
<title>Digital Ocean | Firefiles</title>
</Head>
<Flex px="16px" pt="3">
<IconButton
variant="ghost"
aria-label="back"
icon={<ArrowNarrowLeft />}
mr="3"
onClick={() => router.push("/new")}
/>
<Heading as="h3" size="lg">
Enter your Digital Ocean keys
</Heading>
</Flex>
<Container display="flex" minH="90vh" flexDir="column" justifyContent="center" maxW="lg">
<Flex as="form" onSubmit={listBuckets} flexDir="column" w="full">
<Input
mb="2"
variant="flushed"
placeholder="Access Key ID"
type="text"
value={keyId}
onChange={(e) => setKeyId(e.target.value)}
required
/>
<Input
mb="2"
variant="flushed"
placeholder="Secret Key"
type="text"
value={applicationKey}
onChange={(e) => setApplicationKey(e.target.value)}
required
/>
<Input
mb="2"
variant="flushed"
placeholder="Endpoint - https://<your-region>.digitaloceanspaces.com"
type="text"
value={endpoint}
onChange={(e) => setEndpoint(e.target.value)}
required
/>
<VideoModal src="/digital-ocean-keys-tutorial.mov" />
<Button type="submit" isLoading={loading} colorScheme="green" variant="solid">
Next
</Button>
</Flex>
{buckets?.length > 0 ? (
<>
<Divider my="6" />
<Box>
<Heading as="h4" size="md" mb="2">
Found {buckets.length} buckets:
</Heading>
<Text fontSize="sm">Choose a bucket:</Text>
<Select value={selectedBucket} onChange={(e) => setSelectedBucket(e.target.value)}>
<option>Not Selected</option>
{buckets.map((bucket) => (
<option key={bucket.CreationDate.toString()} value={bucket.Name}>
{bucket.Name}
</option>
))}
</Select>
<Text fontSize="lg" align="center" my="2">
OR
</Text>
<Text fontSize="sm">Create New:</Text>
<Input
mb="2"
variant="flushed"
placeholder="Bucket Name"
type="text"
value={bucketName}
onChange={(e) => {
// Bucket name must not contain spaces or uppercase letters
const text = e.target.value.replace(" ", "").toLowerCase();
setBucketName(text);
}}
required
/>
<Button
mt="2"
w="full"
isLoading={loading}
onClick={createBucket}
colorScheme="green"
variant="solid"
>
Create
</Button>
</Box>
</>
) : null}
</Container>
</>
);
};

export default NewS3;

2 comments on commit 7452e88

@vercel
Copy link

@vercel vercel bot commented on 7452e88 Aug 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 7452e88 Aug 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

firefiles – ./apps/web

usefirefiles.vercel.app
firefiles-git-main-fayd.vercel.app
firefiles-fayd.vercel.app
beta.firefiles.app

Please sign in to comment.