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
1,373 changes: 1,373 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
"@react-oauth/google": "^0.12.1",
"@zk-email/sdk": "1.0.0-2",
"@zk-email/sdk": "1.0.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.11.11",
Expand Down
2 changes: 1 addition & 1 deletion src/app/(bluerpint-list)/BlueprintList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function BlueprintList({ search, filters, sort }: BlueprintListPr
limit: PAGINATION_LIMIT,
status: filters.length > 0 ? filters : undefined,
sort: -1,
sortBy: sort as 'stars' | 'updatedAt',
sortBy: sort as 'stars' | 'updatedAt' | 'totalProofs',
});
} catch (err) {
retryCount++;
Expand Down
7 changes: 7 additions & 0 deletions src/app/(bluerpint-list)/FilterAndSortButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ const FilterAndSortButton = forwardRef<HTMLButtonElement, FilterAndSortButtonPro
handleSort('updatedAt', checked);
}}
/>
<Checkbox
title="Total Proofs"
checked={sort === 'totalProofs'}
onCheckedChange={(checked: boolean) => {
handleSort('totalProofs', checked);
}}
/>
</div>
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(bluerpint-list)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function BlueprintListWrapper() {
const search = searchParams.get('search');
const filters =
(searchParams.get('filter')?.split(',').filter(Boolean) as unknown as Status[]) || [];
const sort = searchParams.get('sort') || '';
const sort = searchParams.get('sort') || 'totalProofs';

return (
<div className="">
Expand Down
2 changes: 1 addition & 1 deletion src/app/[id]/proofs/[proofId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ const ProofInfo = ({ params }: { params: Promise<{ id: string; proofId: string }
/>
}
>
Verify On-chain
Verify
</Button>
<Button
className="flex flex-row gap-2"
Expand Down
4 changes: 4 additions & 0 deletions src/app/components/BlueprintCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ const BlueprintCard = ({ blueprint, setStarred, setUnStarred, starred }: Bluepri
{getStatusName(blueprint.props.status)}
</span>
)}
<span className="flex flex-row items-center gap-1 rounded-lg px-2 py-1 font-medium text-grey-800">
<Image width={16} height={16} src="/assets/Users.svg" alt="views" />
{blueprint.props.totalProofs}
</span>
<button
onClick={onStar}
className="flex flex-row gap-1 rounded-md border border-grey-500 bg-neutral-200 px-2 py-1 text-grey-800"
Expand Down
15 changes: 1 addition & 14 deletions src/app/components/BlueprintTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,6 @@ export const BlueprintTitle = ({
starBlueprint,
}: BlueprintTitleProps) => {
const token = useAuthStore((state) => state.token);
const [numProofs, setNumProofs] = useState(0);

useEffect(() => {
if (!blueprint || !blueprint.getNumOfRemoteProofs) return;
blueprint
.getNumOfRemoteProofs()
.then((remoteProofs) => {
setNumProofs(remoteProofs + (blueprint.props.numLocalProofs || 0) || 0);
})
.catch((err) => {
console.error('Failed to get remote proofs for blueprint: ', err);
});
}, [blueprint]);

const handleStarClick = () => {
if (!token) {
Expand All @@ -56,7 +43,7 @@ export const BlueprintTitle = ({
<div className="flex items-center gap-3 text-sm">
<span className="flex flex-row items-center gap-1 rounded-lg px-2 py-1 font-medium text-grey-800">
<Image width={16} height={16} src="/assets/Users.svg" alt="views" />
{numProofs}
{blueprint.props.totalProofs}
</span>
<button
onClick={handleStarClick}
Expand Down
1 change: 1 addition & 0 deletions src/app/contexts/GoogleAuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const GoogleAuthProvider = ({ children }: ProvidersProps) => {
scope: 'email profile https://www.googleapis.com/auth/gmail.readonly',
flow: 'implicit',
ux_mode: 'redirect',
prompt: 'consent',
} as UseGoogleLoginOptionsImplicitFlow);

const googleLogOut = () => {
Expand Down
53 changes: 33 additions & 20 deletions src/app/create/[id]/createBlueprintSteps/ExtractFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ const ExtractFields = ({
const [regexGeneratedOutputs, setRegexGeneratedOutputs] = useState<string[]>(
Array(store.decomposedRegexes?.length ?? 0).fill('')
);
const [regexGeneratedOutputErrors, setRegexGeneratedOutputErrors] = useState<string[]>(
Array(store.decomposedRegexes?.length ?? 0).fill('')
);

const [isExtractSubjectChecked, setIsExtractSubjectChecked] = useState(false);
const [isExtractReceiverChecked, setIsExtractReceiverChecked] = useState(false);
Expand Down Expand Up @@ -149,32 +152,37 @@ const ExtractFields = ({
revealPrivateFields
);

const outputUpdated =
JSON.stringify(regexOutputs) !== JSON.stringify(regexGeneratedOutputs[index]);

setRegexGeneratedOutputs((prev) => {
const updated = [...prev];
// @ts-ignore
updated[index] = regexOutputs;
return updated;
});

// // update the max length of the regex at that particular index
const decomposedRegexes = [...store.decomposedRegexes];
decomposedRegexes[index].maxLength = regexOutputs[0].length ?? 64;
setField('decomposedRegexes', decomposedRegexes);
setRegexGeneratedOutputErrors((prev) => {
const updated = [...prev];
// @ts-ignore
updated[index] = '';
return updated;
});

// update the max length of the regex at that particular index
// Only update when the output changes, so we can still set maxLength
if (outputUpdated) {
const totalLength = regexOutputs.reduce((acc, cur) => (acc += cur.length), 0);
const decomposedRegexes = [...store.decomposedRegexes];
decomposedRegexes[index].maxLength = totalLength ?? 64;
setField('decomposedRegexes', decomposedRegexes);
}
} catch (error) {
console.error('Error testing decomposed regex:', error);
setRegexGeneratedOutputs((prev) => {
setRegexGeneratedOutputErrors((prev) => {
const updated = [...prev];

if (regex.parts.filter((part) => part.isPublic).length === 0) {
// @ts-ignore
updated[index] = [
'Error: Empty regex — you must define at least one public part for each pattern.',
];
} else {
// @ts-ignore
updated[index] = ['Error: ' + error];
}

// @ts-ignore
updated[index] = 'Error: ' + error;
return updated;
});
}
Expand Down Expand Up @@ -297,7 +305,9 @@ const ExtractFields = ({
if (
!regexGeneratedOutputs.length ||
regexGeneratedOutputs.some((output) =>
Array.isArray(output) ? output.join('').includes('Error') : output.includes('Error')
Array.isArray(output)
? output.join('').includes('Error')
: typeof output === 'string' && output.includes('Error')
)
) {
setCanCompile(false);
Expand Down Expand Up @@ -615,6 +625,9 @@ const ExtractFields = ({
setField('decomposedRegexes', updatedRegexes);

setRegexGeneratedOutputs(regexGeneratedOutputs.filter((_, i) => i !== index));
setRegexGeneratedOutputErrors(
regexGeneratedOutputErrors.filter((_, i) => i !== index)
);
}}
>
Delete
Expand Down Expand Up @@ -834,13 +847,13 @@ const ExtractFields = ({
<Label>Output</Label>
<div
className={`rounded-lg border p-2 text-sm ${
JSON.stringify(regexGeneratedOutputs[index]).includes('Error:')
regexGeneratedOutputErrors[index]
? 'border-red-500 bg-red-100'
: 'border-grey-500 bg-neutral-100'
}`}
>
{JSON.stringify(regexGeneratedOutputs[index]).includes('Error: ')
? JSON.stringify(regexGeneratedOutputs[index])
{regexGeneratedOutputErrors[index]
? JSON.stringify(regexGeneratedOutputErrors[index])
: regexGeneratedOutputs
? `${regex.name}: ${JSON.stringify(regexGeneratedOutputs[index])}`
: ''}
Expand Down
43 changes: 18 additions & 25 deletions src/app/create/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
'use client';

// Test decomposedRegex
/*
[
{
"isPublic": true,
"regexDef": "Hi!"
}
]

*/

import { useCreateBlueprintStore } from './store';

import { use, useEffect, useState } from 'react';
Expand Down Expand Up @@ -190,11 +179,11 @@ const CreateBlueprint = ({ params }: { params: Promise<{ id: string }> }) => {
setDkimSelector(selector);

if (
(store.senderDomain !== senderDomain ||
store.emailQuery !== emailQuery ||
store.emailHeaderMaxLength !== (Math.ceil(headerLength / 64) + 7) * 64 ||
store.emailBodyMaxLength !== (Math.ceil(emailBodyMaxLength / 64) + 7) * 64) &&
!updateFields && id !== 'new'
(store.senderDomain !== senderDomain || store.emailQuery !== emailQuery) &&
store.emailBodyMaxLength !== (Math.ceil(emailBodyMaxLength / 64) + 7) * 64 &&
store.emailHeaderMaxLength !== (Math.ceil(headerLength / 64) + 7) * 64 &&
!updateFields &&
id !== 'new'
) {
setIsConfirmInputsUpdateModalOpen(true);
return;
Expand All @@ -210,12 +199,16 @@ const CreateBlueprint = ({ params }: { params: Promise<{ id: string }> }) => {
}
console.error('Failed to get content from email', err);
if (savedEmls[id]) {
toast.error('Invalid email');
if (typeof err === 'string') {
toast.error(err);
} else {
toast.error('Invalid email');
}
}
return;
}

// Bleurpint was not defined yet, skip testing email against blueprint
// Blueprint was not defined yet, skip testing email against blueprint
if (id === 'new') return;

try {
Expand Down Expand Up @@ -261,7 +254,7 @@ const CreateBlueprint = ({ params }: { params: Promise<{ id: string }> }) => {
if (!savedEmls[id]) {
setIsFileInvalid(false);
}
}, [JSON.stringify(store.decomposedRegexes), savedEmls[id], revealPrivateFields]);
}, [savedEmls[id]]);

// Create a debounced version of the DKIM verification
const debouncedVerifyDKIM = debounce(async (domain: string, selector: string | null) => {
Expand Down Expand Up @@ -302,6 +295,12 @@ const CreateBlueprint = ({ params }: { params: Promise<{ id: string }> }) => {
};
}, [JSON.stringify(store.senderDomain), dkimSelector, step]);

useEffect(() => {
if (step === '2') {
genrateHighlightRegexContent();
}
}, [JSON.stringify(store.decomposedRegexes), savedEmls[id]]);

const isNextButtonDisabled = () => {
if (!savedEmls[id] || isFileInvalid) {
return true;
Expand Down Expand Up @@ -364,12 +363,6 @@ const CreateBlueprint = ({ params }: { params: Promise<{ id: string }> }) => {
);
}

useEffect(() => {
if (step === '2') {
genrateHighlightRegexContent();
}
}, [JSON.stringify(store.decomposedRegexes), savedEmls[id]]);

const genrateHighlightRegexContent = async () => {
if (!savedEmls[id]) {
return;
Expand Down
10 changes: 5 additions & 5 deletions src/app/create/[id]/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ const initialState: BlueprintProps = {
tags: [],
emailQuery: '',
circuitName: '',
ignoreBodyHashCheck: false,
ignoreBodyHashCheck: true,
shaPrecomputeSelector: '',
emailBodyMaxLength: 1024,
emailHeaderMaxLength: 10240,
removeSoftLinebreaks: true,
removeSoftLinebreaks: false,
githubUsername: '',
senderDomain: '',
enableHeaderMasking: false,
Expand Down Expand Up @@ -135,9 +135,9 @@ export const useCreateBlueprintStore = create<CreateBlueprintState>()(
const savedEmls = JSON.parse(localStorage.getItem('blueprintEmls') || '{}');

// Page logic should already prevent saving a draft without having a file
if (!state.file && !savedEmls[state.id ?? 'new']) {
throw new Error('Can only save a draft with an example email provided');
}
// if (!state.file && !savedEmls[state.id ?? 'new']) {
// throw new Error('Can only save a draft with an example email provided');
// }

// Remove functions from the state data and clone
const data = JSON.parse(
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="">
<html lang="" suppressHydrationWarning>
<body className={`${fustat.className} flex min-h-screen flex-col bg-[#F5F3EF] antialiased`}>
<Suspense>
<ThemeProvider attribute="class">
Expand Down
2 changes: 1 addition & 1 deletion src/app/user/blueprints/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function BlueprintListWrapper() {

const filters =
(searchParams.get('filter')?.split(',').filter(Boolean) as unknown as Status[]) || [];
const sort = searchParams.get('sort') || '';
const sort = searchParams.get('sort') || 'totalProofs';

return (
<div className="">
Expand Down
40 changes: 37 additions & 3 deletions src/app/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,21 +148,55 @@ function debounce<T extends (...args: any[]) => any>(
return debounced as T & { cancel: () => void };
}

// TODO: This should be moved to the SDK
const findOrCreateDSP = async (file: File) => {
const content = await getFileContent(file);
const { senderDomain, selector } = await extractEMLDetails(content);

const dkimPair = getSenderDomainAndSelectorPair(content);
if (!dkimPair) {
return null;
}
const response = await fetch('https://archive.zk.email/api/dsp', {
method: 'POST',
body: JSON.stringify({
domain: senderDomain,
selector: selector,
domain: dkimPair.domain,
selector: dkimPair.selector,
}),
});

return response;
};

// TODO: This should be moved to the SDK
const getSenderDomainAndSelectorPair = (emlContent: string) => {
const headerLines: string[] = [];
const lines = emlContent.split("\n");
for (const line of lines) {
if (line.trim() === "") break;
// If line starts with whitespace, it's a continuation of previous header
if (line.startsWith(" ") || line.startsWith("\t")) {
headerLines[headerLines.length - 1] += line.trim();
} else {
headerLines.push(line);
}
}

// Then look for DKIM-Signature in the joined headers
for (const line of headerLines) {
if (line.includes("DKIM-Signature")) {
const selectorMatch = line.match(/s=([^;]+)/);
const domainMatch = line.match(/d=([^;]+)/);
if (selectorMatch && domainMatch) {
return {
selector: selectorMatch[1].trim(),
domain: domainMatch[1].trim(),
};
}
}
}
return null;
};

export {
getStatusColorLight,
getStatusIcon,
Expand Down
Loading