Skip to content

Commit

Permalink
Merge pull request #739 from panoratech/google-drive-integration-updates
Browse files Browse the repository at this point in the history
feat: Google drive, OneDrive Connectors
  • Loading branch information
rflihxyz authored Dec 24, 2024
2 parents 828989d + c268c24 commit 2483653
Show file tree
Hide file tree
Showing 79 changed files with 5,307 additions and 1,951 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Panora supports integration with the following objects across multiple platforms

| | Drives | Files | Folders | Groups | Users | Permissions | Shared Links |
|-----------------------------------------------|:--------:|:-----:|:-----:|:-----------:|:-----:|:-----:|:---------:|
| Google Drive | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | |
| Google Drive | ✔️ | ✔️ | ✔️ | | | ✔️ | |
| Box | | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |
| Dropbox | | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |
| OneDrive | ✔️ | ✔️ | ✔️| ✔️ | ✔️ | | |
Expand Down
29 changes: 26 additions & 3 deletions apps/webapp/src/app/(Dashboard)/api-keys/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ import { useForm } from "react-hook-form"
import { DataTableLoading } from "@/components/shared/data-table-loading";
import {CustomHeading} from "@/components/shared/custom-heading";
import { useColumns } from "@/components/ApiKeys/columns";
import { PlusCircle } from "lucide-react";
import { PlusCircle, Copy, Check } from "lucide-react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { useQueryClient } from "@tanstack/react-query";
import { cn } from "@/lib/utils";

const formSchema = z.object({
apiKeyIdentifier: z.string().min(2, {
Expand Down Expand Up @@ -251,8 +252,30 @@ export default function Page() {
This key will only be shown for the next minute. Please save it now.
</DialogDescription>
</DialogHeader>
<div className="mt-4">
<strong>API Key:</strong> <p>{newApiKey?.key}</p>
<div className="mt-4 flex items-center justify-between gap-2 rounded-md border p-2">
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold">
{newApiKey?.key}
</code>
<Button
variant="ghost"
size="icon"
onClick={() => {
if (newApiKey?.key) {
navigator.clipboard.writeText(newApiKey.key);
toast.success("API key copied to clipboard");
}
}}
className={cn(
"h-8 w-8",
"hover:bg-muted",
"focus-visible:ring-1",
"focus-visible:ring-ring",
"focus-visible:ring-offset-0"
)}
>
<Copy className="h-4 w-4" />
<span className="sr-only">Copy API key</span>
</Button>
</div>
<DialogFooter>
<Button size="sm" className="h-7 gap-1" onClick={() => setIsKeyModalOpen(false)}>Close</Button>
Expand Down
92 changes: 61 additions & 31 deletions apps/webapp/src/app/(Dashboard)/configuration/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import useUpdatePullFrequency from "@/hooks/create/useCreatePullFrequency";
import { toast } from "sonner";
import { useQueryClient } from "@tanstack/react-query";
import { Loader2 } from "lucide-react";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";

const frequencyOptions = [
{ label: '5 min', value: 300 },
Expand All @@ -63,7 +65,9 @@ export default function Page() {
const [open, setOpen] = useState(false);
const [localFrequencies, setLocalFrequencies] = useState<Record<string, string>>({});
const [loadingStates, setLoadingStates] = useState<Record<string, boolean>>({});

const [dialogOpen, setDialogOpen] = useState(false);
const [confirmText, setConfirmText] = useState('');
const [selectedVertical, setSelectedVertical] = useState<string | null>(null);

const queryClient = useQueryClient();

Expand Down Expand Up @@ -114,39 +118,26 @@ export default function Page() {
if (pullFrequencies) {
const initialFrequencies = VERTICALS.reduce((acc, vertical) => {
const value = pullFrequencies[vertical as keyof typeof pullFrequencies];
acc[vertical] = typeof value === 'string' ? value : '0';
acc[vertical] = value?.toString() || '0';
return acc;
}, {} as Record<string, string>);
setLocalFrequencies(initialFrequencies);
}
}, [pullFrequencies]);

const handleFrequencyChange = (vertical: string, value: string) => {
setLocalFrequencies(prev => ({ ...prev, [vertical]: value }));
};



const handleFrequencyChangeAndSave = async (vertical: string, value: string) => {
handleFrequencyChange(vertical, value);
await saveFrequency(vertical, value);
};

const saveFrequency = async (vertical: string, value: string) => {
setLoadingStates(prev => ({ ...prev, [vertical]: true }));
const frequency = parseInt(value, 10);
const updateData = { [vertical]: frequency };

try {
const frequency = parseInt(value, 10);
console.log("frequency being saved: " + frequency);
const updateData = { [vertical]: frequency };

await toast.promise(
createPullFrequencyPromise(updateData),
{
loading: 'Updating...',
success: (data: any) => {
queryClient.setQueryData<any>(['pull_frequencies'], (oldData: any) => ({
...oldData,
[vertical]: frequency.toString(),
success: () => {
setLocalFrequencies(prev => ({
...prev,
[vertical]: value
}));
return frequency === 0 ? `${vertical} sync deactivated` : `Frequency saved for ${vertical}`;
},
Expand All @@ -155,16 +146,56 @@ export default function Page() {
);
} catch (error) {
console.error(`Error updating ${vertical} pull frequency:`, error);
} finally {
setLoadingStates(prev => ({ ...prev, [vertical]: false }));
}
};



const handleSuspendClick = (vertical: string) => {
setSelectedVertical(vertical);
setConfirmText('');
setDialogOpen(true);
};

const handleConfirmSuspend = async () => {
if (selectedVertical && confirmText.toLowerCase() === 'suspend') {
await handleFrequencyChangeAndSave(selectedVertical, '0');
setDialogOpen(false);
setConfirmText('');
}
};

return (

<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Sync Suspension</DialogTitle>
<DialogDescription>
This will stop all automatic syncs for {selectedVertical?.toUpperCase()}. To confirm, please type &quot;suspend&quot; below.
</DialogDescription>
</DialogHeader>
<Input
value={confirmText}
onChange={(e) => setConfirmText(e.target.value)}
placeholder="Type 'suspend' to confirm"
/>
<DialogFooter>
<Button
variant="ghost"
onClick={() => setDialogOpen(false)}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={handleConfirmSuspend}
disabled={confirmText.toLowerCase() !== 'suspend'}
>
Confirm Suspension
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex items-center justify-between space-y-2">
<Heading
Expand Down Expand Up @@ -245,14 +276,14 @@ export default function Page() {
<CardContent>
{VERTICALS.map(vertical => (
<div key={vertical} className="mb-4">
<label className="block text-sm font-medium text-white mb-1">
<label className="block text-sm font-medium mb-1">
{vertical.toUpperCase()}
</label>
<div className="flex items-center space-x-2">
<Select
value={localFrequencies[vertical] || '0'}
onValueChange={(value: string) => handleFrequencyChangeAndSave(vertical, value)}
>
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
Expand All @@ -266,13 +297,12 @@ export default function Page() {
</Select>
{localFrequencies[vertical] && localFrequencies[vertical] !== '0'&& (
<Button
onClick={() => handleFrequencyChangeAndSave(vertical, '0')}
onClick={() => handleSuspendClick(vertical)}
size="sm"
variant="destructive"
className="h-7"
>
{localFrequencies[vertical]}
Deactivate
Suspend Sync
</Button>
)}
</div>
Expand Down
111 changes: 73 additions & 38 deletions apps/webapp/src/app/b2c/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use client';

import CreateUserForm from "@/components/Auth/CustomLoginComponent/CreateUserForm";
import LoginUserForm from "@/components/Auth/CustomLoginComponent/LoginUserForm";
import {
Expand All @@ -13,74 +14,108 @@ import Cookies from 'js-cookie';
import useProfileStore from "@/state/profileStore";
import useUser from "@/hooks/get/useUser";
import { Button } from "@/components/ui/button";
import { useTheme } from "next-themes";

export default function Page() {
const [userInitialized,setUserInitialized] = useState(true)
const {mutate} = useUser()
const router = useRouter()
const {profile} = useProfileStore();
const [activeTab, setActiveTab] = useState('login');
const { theme, systemTheme } = useTheme();
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
// Get the hash from URL and set the active tab accordingly
const hash = window.location.hash.replace('#', '');
if (hash === 'signup') {
setActiveTab('create');
} else if (hash === 'login') {
setActiveTab('login');
}
}, []);

const currentTheme = theme === 'system' ? systemTheme : theme;

// Handle tab changes and update URL
const handleTabChange = (value: string) => {
setActiveTab(value);
const hash = value === 'create' ? 'signup' : 'login';
window.history.replaceState(null, '', `#${hash}`);
};

useEffect(() => {
if(profile)
{
router.replace('/connections');
}

},[profile]);
},[profile, router]);

useEffect(() => {

if(!Cookies.get('access_token'))
{
setUserInitialized(false);
}
{
setUserInitialized(false);
}

if(Cookies.get('access_token') && !profile)
{
mutate(Cookies.get('access_token'),{
onError: () => setUserInitialized(false)
})
}
},[profile, mutate])

// if(profile)
// {
// router.replace('/connections');
// }

},[])
if (!mounted) {
return null;
}

return (
<>

{!userInitialized ?
(
<div className='min-h-screen grid lg:grid-cols-2 mx-auto text-left'>
<div className='flex-1 flex flex-col py-12 sm:items-center lg:flex-none lg:px-20 xl:px-24'>
<div className="w-[400px]">
<img src="/logo.png" className='w-14' />
<div className='min-h-screen flex items-center'>
<div className='w-full max-w-[1200px] px-4 mx-auto'>
<div className='flex items-start justify-between gap-8'>
<div className='w-[450px] h-[600px]'>
<Tabs value={activeTab} onValueChange={handleTabChange} className="h-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="login">Login</TabsTrigger>
<TabsTrigger value="create">Create Account</TabsTrigger>
</TabsList>
<div className="mt-6">
<TabsContent value="login" className="mt-0 h-[calc(100%-48px)]">
<LoginUserForm/>
</TabsContent>
<TabsContent value="create" className="mt-0 h-[calc(100%-48px)]">
<CreateUserForm/>
</TabsContent>
</div>
</Tabs>
</div>

<div className='w-[450px] space-y-8'>
<div>
<img
src={currentTheme === "dark" ? "/logo-panora-white-hq.png" : "/logo-panora-black.png"}
className='w-48'
alt="Panora"
/>
</div>
<div className="space-y-4">
<p className="text-muted-foreground">
Connect your warehouse to any e-commerce platform and let AI automate data entry into your WMS &amp; ERPs. Add revenue, not complexity to your operations.
</p>
<p className="text-muted-foreground">
Use one unified API to manage orders across Shopify, Amazon, and more. Let AI handle your inventory updates while you focus on growth.
</p>
<p className="text-muted-foreground">You&apos;ll wonder how you ever managed without it.</p>
<p className="text-sm text-muted-foreground">
Don&apos;t have an account? Create one now
</p>
</div>
</div>
</div>
<Tabs defaultValue="login" className="w-[400px] space-y-4">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="login">Login</TabsTrigger>
<TabsTrigger value="create">Create Account</TabsTrigger>
</TabsList>
<TabsContent value="login">
<LoginUserForm/>
</TabsContent>
<TabsContent value="create">
<CreateUserForm/>
</TabsContent>
</Tabs>

{activeTab === 'forgot-password' && (
<Button variant="link" onClick={() => setActiveTab('login')}>
Back to Login
</Button>
)}
</div>
<div className='hidden lg:block relative flex-1'>
<img className='absolute inset-0 h-full w-full object-cover border-l' src="/bgbg.jpeg" alt='Login Page Image' />
</div>
</div>
) :
Expand Down
Loading

0 comments on commit 2483653

Please sign in to comment.