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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: 'pnpm'

- name: Install dependencies
Expand Down
23 changes: 23 additions & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "desktop",
"productName": "Kareer",
"private": true,
"version": "0.1.0",
"description": "Desktop application",
"main": "dist/src/main.js",
"scripts": {
"build": "tsc",
"check-types": "tsc --noEmit",
"lint": "tsc --noEmit",
"make:mac": "pnpm build && node dist/scripts/make-macos.js",
"start": "pnpm build && electron ."
},
"devDependencies": {
"@electron/packager": "^20.0.4",
"@kareer/typescript-config": "workspace:*",
"@kds/icons": "workspace:*",
"@types/node": "catalog:",
"electron": "^43.2.0",
"typescript": "catalog:"
}
}
130 changes: 130 additions & 0 deletions apps/desktop/scripts/make-macos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

import { packager, type OfficialArch } from '@electron/packager';

import { productName, version } from '../package.json';

const desktopDir = path.resolve(__dirname, '../..');
const outDir = path.join(desktopDir, 'out');
const arch = (process.argv[2] ?? process.arch) as OfficialArch;

const run = (command: string, args: string[]) =>
execFileSync(command, args, { stdio: 'inherit' });

const makeIcon = (tempDir: string) => {
const logoPath = require.resolve('@kds/icons/assets/logo.svg');
const svgPath = path.join(tempDir, 'logo.svg');
const logo = fs
.readFileSync(logoPath, 'utf8')
.replace(/<svg[^>]*>|<\/svg>/g, '');
const svg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<rect x="64" y="64" width="896" height="896" rx="200" fill="white"/>
<g transform="translate(152 152) scale(30)">${logo}</g>
</svg>`;

fs.writeFileSync(svgPath, svg);

const previewDir = path.join(tempDir, 'preview');
fs.mkdirSync(previewDir);
run('/usr/bin/qlmanage', ['-t', '-s', '1024', '-o', previewDir, svgPath]);

const sourcePng = path.join(previewDir, 'logo.svg.png');
const iconsetDir = path.join(tempDir, `${productName}.iconset`);
fs.mkdirSync(iconsetDir);

for (const [name, size] of [
['icon_16x16.png', 16],
['icon_16x16@2x.png', 32],
['icon_32x32.png', 32],
['icon_32x32@2x.png', 64],
['icon_128x128.png', 128],
['icon_128x128@2x.png', 256],
['icon_256x256.png', 256],
['icon_256x256@2x.png', 512],
['icon_512x512.png', 512],
['icon_512x512@2x.png', 1024],
] as const) {
run('/usr/bin/sips', [
'-z',
String(size),
String(size),
sourcePng,
'--out',
path.join(iconsetDir, name),
]);
}

const iconPath = path.join(tempDir, `${productName}.icns`);
run('/usr/bin/iconutil', ['-c', 'icns', iconsetDir, '-o', iconPath]);
return iconPath;
};

const main = async () => {
if (process.platform !== 'darwin') {
throw new Error('macOS에서만 DMG를 만들 수 있습니다.');
}

const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kareer-macos-'));

try {
const [packageDir] = await packager({
appBundleId: 'com.teamkareer.kareer',
appCategoryType: 'public.app-category.business',
arch,
asar: true,
dir: desktopDir,
icon: makeIcon(tempDir),
ignore: [
/\/node_modules(?:\/|$)/,
/\/scripts(?:\/|$)/,
/\.test\.js$/,
/\.ts$/,
],
name: productName,
out: outDir,
overwrite: true,
platform: 'darwin',
prune: false,
});

if (!packageDir) {
throw new Error('Electron 앱 패키징 결과를 찾을 수 없습니다.');
}

const appPath = path.join(packageDir, `${productName}.app`);
run('/usr/bin/codesign', ['--force', '--deep', '--sign', '-', appPath]);

const dmgSourceDir = path.join(tempDir, 'dmg');
fs.mkdirSync(dmgSourceDir);
run('/usr/bin/ditto', [
appPath,
path.join(dmgSourceDir, `${productName}.app`),
]);
fs.symlinkSync('/Applications', path.join(dmgSourceDir, 'Applications'));

const makeDir = path.join(outDir, 'make');
const dmgPath = path.join(makeDir, `${productName}-${version}-${arch}.dmg`);
fs.mkdirSync(makeDir, { recursive: true });
run('/usr/bin/hdiutil', [
'create',
'-volname',
productName,
'-srcfolder',
dmgSourceDir,
'-ov',
'-format',
'UDZO',
dmgPath,
]);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
};

main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
67 changes: 67 additions & 0 deletions apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { app, BrowserWindow, shell } from 'electron';

import { productName } from '../package.json';
import { isAllowedNavigationUrl, isHttpsUrl } from './url-policy.js';

const WEB_URL = 'https://app.ka-reer.com';

app.setName(productName);

const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 1440,
height: 900,
minWidth: 1024,
minHeight: 700,
autoHideMenuBar: true,
show: false,
webPreferences: {
contextIsolation: true,
devTools: !app.isPackaged,
nodeIntegration: false,
sandbox: true,
},
});

mainWindow.once('ready-to-show', () => mainWindow.show());

mainWindow.webContents.on('will-navigate', (event, url) => {
if (!isAllowedNavigationUrl(url)) {
event.preventDefault();
}
});

mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (isHttpsUrl(url)) {
void shell.openExternal(url);
}

return { action: 'deny' };
});

mainWindow.webContents.session.setPermissionRequestHandler(
(_webContents, _permission, callback) => callback(false),
);

void mainWindow
.loadURL(WEB_URL)
.catch((error) =>
console.error(`${productName} 웹을 불러오지 못했습니다.`, error),
);
};

app.whenReady().then(() => {
createWindow();

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
21 changes: 21 additions & 0 deletions apps/desktop/src/url-policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const ALLOWED_NAVIGATION_ORIGINS = new Set([
'https://app.ka-reer.com',
'https://api.ka-reer.com',
'https://accounts.google.com',
]);

export const isAllowedNavigationUrl = (value: string) => {
try {
return ALLOWED_NAVIGATION_ORIGINS.has(new URL(value).origin);
} catch {
return false;
}
};

export const isHttpsUrl = (value: string) => {
try {
return new URL(value).protocol === 'https:';
} catch {
return false;
}
};
12 changes: 12 additions & 0 deletions apps/desktop/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "@kareer/typescript-config/base.json",
"compilerOptions": {
"declaration": false,
"declarationMap": false,
"outDir": "dist",
"rootDir": ".",
"types": ["node"]
},
"include": ["src", "scripts"],
"exclude": ["node_modules", "dist", "out"]
}
1 change: 1 addition & 0 deletions apps/landing/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_FORMSPREE_FORM_ID=your_form_id
3 changes: 3 additions & 0 deletions apps/landing/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { config } from '@kareer/eslint-config/react';

export default config;
37 changes: 37 additions & 0 deletions apps/landing/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link
rel="icon"
type="image/png"
href="/src/assets/pabicon.webp"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta property="og:title" content="kareer" />
<meta
property="og:description"
content="The fastest roadmap to your vision"
/>
<meta property="og:url" content="https://ka-reer.com" />
<meta property="og:image" content="https://ka-reer.com/og.png" />
<link
rel="preload"
as="style"
crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css"
onload="this.rel = 'stylesheet'"
/>
<noscript>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css"
/>
</noscript>
<title>kareer</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
37 changes: 37 additions & 0 deletions apps/landing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "landing",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"check-types": "tsc --noEmit"
},
"dependencies": {
"@kds/icons": "workspace:*",
"@kds/ui": "workspace:*",
"i18next": "^25.8.13",
"react": "catalog:",
"react-dom": "catalog:",
"react-i18next": "^16.5.4"
},
"devDependencies": {
"@kareer/eslint-config": "workspace:*",
"@kareer/typescript-config": "workspace:*",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vanilla-extract/css": "catalog:",
"@vanilla-extract/recipes": "catalog:",
"@vanilla-extract/vite-plugin": "catalog:",
"@vitejs/plugin-react": "catalog:",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-svgr": "catalog:"
}
}
Binary file added apps/landing/public/og.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/landing/src/assets/bg_why_kareer.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/landing/src/assets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as bg_why_kareer } from './bg_why_kareer.jpg';
Binary file added apps/landing/src/assets/pabicon.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions apps/landing/src/components/badge/badge.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { themeVars, typography } from '@kds/ui/styles';
import { recipe } from '@vanilla-extract/recipes';

export const badge = recipe({
base: {
...typography.cap1_sb_12,
display: 'inline-flex',
alignItems: 'center',
gap: '0.6rem',
borderStyle: 'solid',
borderWidth: '1px',
borderRadius: '99px',
},
variants: {
tone: {
light: {
padding: '0.5rem 1.6rem',
borderColor: themeVars.color.primary[300],
color: themeVars.color.primary[500],
backgroundColor: themeVars.color.primary[100],
},
dark: {
padding: '0.6rem 1.6rem',
borderColor: themeVars.color.primary[700],
color: themeVars.color.primary[300],
backgroundColor: 'rgba(59, 110, 248, 0.25)',
},
},
},
defaultVariants: {
tone: 'light',
},
});
Loading