Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/blog-platform/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/blog-platform",
"version": "v3.7.0-alpha.2",
"version": "v3.7.1-beta.1",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
2 changes: 1 addition & 1 deletion apps/docs-platform/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/docs-platform",
"version": "v3.7.0-alpha.2",
"version": "v3.7.1-beta.1",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/server",
"version": "v3.7.0-alpha.2",
"version": "v3.7.1-beta.1",
"description": "VeriWorkly Resume Backend API",
"main": "dist/index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion apps/site/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/site",
"version": "v3.7.0-alpha.2",
"version": "v3.7.1-beta.1",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
108 changes: 54 additions & 54 deletions apps/studio/.gitignore
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# except example file
!.env.example
!.env.docker.example
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# fumadocs generated files
.source
# testing
/test-results
/app/test
/app/og-generator
/api-index
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

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

# env files (can opt-in for committing if needed)
.env*

# except example file
!.env.example
!.env.docker.example

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# fumadocs generated files
.source

# testing
/test-results
/app/test
/app/og-generator
/api-index
34 changes: 34 additions & 0 deletions apps/studio/features/cover-letter/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { BaseDocument } from "@/features/documents/core/types";

import type { CoverLetterContent } from "./types";

export const COVER_LETTER_TEMPLATE_ID = "cover-letter-classic";

export function createDefaultCoverLetter(id: string): BaseDocument<CoverLetterContent> {
const now = new Date().toISOString();
return {
id,
type: "COVER_LETTER",
title: "Cover Letter",
templateId: COVER_LETTER_TEMPLATE_ID,
updatedAt: now,
sync: {
enabled: false,
status: "local-only",
cloudDocumentId: null,
lastSyncedAt: null,
revision: 1,
},
content: {
recipientName: "",
recipientTitle: "",
companyName: "",
subject: "",
greeting: "Dear Hiring Manager,",
opening: "",
body: "",
closing: "Sincerely,",
signature: "",
},
};
}
44 changes: 44 additions & 0 deletions apps/studio/features/cover-letter/editor/CoverLetterEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { useMemo } from "react";

import {
loadDocumentById,
saveDocument,
} from "@/features/documents/services/document-workspace-service";
import type { CoverLetterContent } from "@/features/cover-letter/types";

interface Props {
documentId: string;
}

export default function CoverLetterEditor({ documentId }: Props) {
const doc = useMemo(() => loadDocumentById("COVER_LETTER", documentId), [documentId]);

if (!doc) return <p className="text-muted-foreground text-sm">Cover letter not found.</p>;

const content = doc.content as CoverLetterContent;

return (
<div className="mx-auto max-w-3xl space-y-4 py-8">
<h1 className="text-2xl font-semibold">{doc.title}</h1>
<textarea
className="min-h-96 w-full rounded-md border p-4"
value={[content.greeting, content.opening, content.body, content.closing, content.signature]
.filter(Boolean)
.join("\n\n")}
onChange={(event) => {
const value = event.target.value;
saveDocument({
...doc,
updatedAt: new Date().toISOString(),
content: {
...content,
body: value,
},
});
}}
/>
</div>
);
}
26 changes: 26 additions & 0 deletions apps/studio/features/cover-letter/render/pdf.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Page, Text, View, Document, StyleSheet } from "@react-pdf/renderer";

import type { CoverLetterContent } from "@/features/cover-letter/types";

const styles = StyleSheet.create({
page: { padding: 40, fontSize: 11, lineHeight: 1.5 },
paragraph: { marginBottom: 10 },
});

export function CoverLetterPdf({ content }: { content: CoverLetterContent }) {
return (
<Document>
<Page size="A4" style={styles.page}>
<View>
{[content.greeting, content.opening, content.body, content.closing, content.signature]
.filter(Boolean)
.map((line, index) => (
<Text key={index} style={styles.paragraph}>
{line}
</Text>
))}
</View>
</Page>
</Document>
);
}
7 changes: 7 additions & 0 deletions apps/studio/features/cover-letter/render/web.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { CoverLetterContent } from "@/features/cover-letter/types";

export function renderCoverLetterWeb(content: CoverLetterContent) {
return [content.greeting, content.opening, content.body, content.closing, content.signature]
.filter(Boolean)
.join("\n\n");
}
53 changes: 53 additions & 0 deletions apps/studio/features/cover-letter/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { BaseDocument } from "@/features/documents/core/types";

import type { CoverLetterContent } from "./types";

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}

function asText(value: unknown): string {
return typeof value === "string" ? value : "";
}

export function parseCoverLetterDocument(input: unknown): BaseDocument<CoverLetterContent> | null {
if (!isRecord(input) || input.type !== "COVER_LETTER") return null;
if (typeof input.id !== "string" || typeof input.templateId !== "string") return null;

const contentRaw = isRecord(input.content) ? input.content : {};
return {
id: input.id,
type: "COVER_LETTER",
title: asText(input.title) || "Cover Letter",
templateId: input.templateId,
updatedAt: asText(input.updatedAt) || new Date().toISOString(),
sync: isRecord(input.sync)
? {
enabled: Boolean(input.sync.enabled),
status: (input.sync.status as BaseDocument["sync"]["status"]) ?? "local-only",
cloudDocumentId:
typeof input.sync.cloudDocumentId === "string" ? input.sync.cloudDocumentId : null,
lastSyncedAt:
typeof input.sync.lastSyncedAt === "string" ? input.sync.lastSyncedAt : null,
revision: typeof input.sync.revision === "number" ? input.sync.revision : 1,
}
: {
enabled: false,
status: "local-only",
cloudDocumentId: null,
lastSyncedAt: null,
revision: 1,
},
content: {
recipientName: asText(contentRaw.recipientName),
recipientTitle: asText(contentRaw.recipientTitle),
companyName: asText(contentRaw.companyName),
subject: asText(contentRaw.subject),
greeting: asText(contentRaw.greeting),
opening: asText(contentRaw.opening),
body: asText(contentRaw.body),
closing: asText(contentRaw.closing),
signature: asText(contentRaw.signature),
},
};
}
11 changes: 11 additions & 0 deletions apps/studio/features/cover-letter/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface CoverLetterContent {
recipientName: string;
recipientTitle: string;
companyName: string;
subject: string;
greeting: string;
opening: string;
body: string;
closing: string;
signature: string;
}
35 changes: 35 additions & 0 deletions apps/studio/features/formal-letter/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { BaseDocument } from "@/features/documents/core/types";

import type { FormalLetterContent } from "./types";

export const FORMAL_LETTER_TEMPLATE_ID = "formal-letter-modern";

export function createDefaultFormalLetter(id: string): BaseDocument<FormalLetterContent> {
const now = new Date().toISOString();
return {
id,
type: "FORMAL_LETTER",
title: "Formal Letter",
templateId: FORMAL_LETTER_TEMPLATE_ID,
updatedAt: now,
sync: {
enabled: false,
status: "local-only",
cloudDocumentId: null,
lastSyncedAt: null,
revision: 1,
},
content: {
senderName: "",
senderAddress: "",
receiverName: "",
receiverAddress: "",
date: "",
subject: "",
greeting: "Dear Sir/Madam,",
body: "",
closing: "Yours faithfully,",
signature: "",
},
};
}
Loading
Loading