Skip to content

Commit

Permalink
๐ŸŒ ๐Ÿฅƒ ๐Ÿฅ™ ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใƒ•ใ‚ฉใƒผใƒ ใ‚’ไฝœใฃใŸ
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-20 committed Dec 2, 2024
1 parent 51e916e commit 3699366
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 4 deletions.
12 changes: 12 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ export const en = {
'copy-to-clipboard': 'Copy to clipboard',
},
common: {
header: {
'feedback-button': 'Feedback',
feedback: {
title: 'Feedback',
description: 'Please feel free to give us your feedback.',
name: 'Name (optional)',
feedback: 'Feedback',
submit: 'Submit',
},
'discord-button': 'Discord',
'twitter-button': "Developer's Twitter",
},
'update-announcement': {
label: 'DiceSpec have been updated to v2.',
v1: 'The original v1 can be accessed here.',
Expand Down
16 changes: 16 additions & 0 deletions src/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,22 @@ export const ja = {
'copy-to-clipboard': 'ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใซใ‚ณใƒ”ใƒผ',
},
common: {
header: {
'feedback-button': 'ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏ',
feedback: {
title: 'ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏ',
description:
'ใ€Œใ“ใ“ใ‚’ใ“ใ†ใ„ใ†้ขจใซๆ”นๅ–„ใ—ใฆๆฌฒใ—ใ„๏ผใ€ใ€Œใชใ‚“ใ‹ใƒใ‚ฐใฃใŸใ€ใ€Œใ“ใฎๆฉŸ่ƒฝใŒๆฌฒใ—ใ„๏ผใ€ใจใ„ใฃใŸใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใ‚’ใŠๅฏ„ใ›ใใ ใ•ใ„ (ใชใ‚‹ในใๅ…ทไฝ“็š„ใซๆ›ธใ„ใฆใ‚‚ใ‚‰ใˆใ‚‹ใจๅฌ‰ใ—ใ„ใงใ™๏ผ)',
name: 'ๅๅ‰ (ไปปๆ„)',
feedback: 'ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏ',
submit: '้€ไฟก',
submitted: 'ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใ‚’้€ไฟกใ—ใพใ—ใŸ๏ผ',
'submitted-description': 'ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใ‚’้€ไฟกใ—ใฆใ„ใŸใ ใใ‚ใ‚ŠใŒใจใ†ใ”ใ–ใ„ใพใ™๏ผ',
error: 'ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใฎ้€ไฟกใซๅคฑๆ•—ใ—ใพใ—ใŸ',
},
'discord-button': 'ใ‚ตใƒใƒผใƒˆDiscord',
'twitter-button': 'ไฝœ่€…ใฎใคใ„ใฃใŸ',
},
'update-announcement': {
label: 'ใƒ€ใ‚คใ‚นใ‚นใƒšใƒƒใ‚ฏใฏv2ใซใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ•ใ‚Œใพใ—ใŸใ€‚',
v1: 'ใ‚‚ใจใฎv1ใฏใ“ใ“ใ‹ใ‚‰ใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™ใ€‚',
Expand Down
108 changes: 104 additions & 4 deletions src/shared/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,55 @@
import { IconBrandDiscord, IconBrandX } from '@tabler/icons-react';
import { IconBrandDiscord, IconBrandX, IconMessageReply } from '@tabler/icons-react';
import { t } from 'i18next';
import type { ComponentProps, FC } from 'react';
import { useState, type ComponentProps, type FC } from 'react';
import { twMerge } from 'tailwind-merge';
import { CustomLink } from '@/shared/components/elements/CustomLink';
import { Button } from '@/shared/components/ui/button';
import { appVersion } from '@/shared/lib/const';
import TitleLogoJP from '/public/title-logo.svg';
import TitleLogoEN from '/public/title-logo-en.svg';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/shared/components/ui/dialog';
import { Input } from '@/shared/components/ui/input';
import { Label } from '@/shared/components/ui/label';
import { Textarea } from '@/shared/components/ui/textarea';
import { useToast } from '@/shared/components/ui/use-toast';
import { sendFeedback } from '@/shared/lib/webhook';

export const Header: FC<ComponentProps<'header'>> = ({ className, ...props }) => {
const TitleLogo = t('lang') === 'en' ? TitleLogoEN : TitleLogoJP;
const [open, setOpen] = useState(false);
const [name, setName] = useState('');
const [feedback, setFeedback] = useState('');
const { toast } = useToast();

const feedbackSubmitHandler = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
sendFeedback({
name: e.currentTarget['feedback-form-name'].value,
feedback: e.currentTarget['feedback-form-feedback'].value,
});
setOpen(false);
toast({
title: t('common:header.feedback.submitted'),
description: t('common:header.feedback.submitted-description'),
variant: 'default',
});
} catch (err) {
toast({
title: t('common:header.feedback.error'),
variant: 'destructive',
});
}
};

return (
<header
className={twMerge('flex items-center justify-between border-b px-4 py-2 max-sm:py-1', className)}
Expand All @@ -22,12 +62,72 @@ export const Header: FC<ComponentProps<'header'>> = ({ className, ...props }) =>
<div className="max-sm:text-sm">v{appVersion}</div>
</div>
<div className="flex gap-2">
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button
variant="outline"
size="icon"
className="h-6 w-6 sm:h-8 sm:w-8"
title={t('common:header.feedback-button')}
onClick={() => setOpen(true)}
>
<IconMessageReply className="h-4 w-5 sm:h-5 sm:w-5" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-2xl">
<form onSubmit={feedbackSubmitHandler}>
<DialogHeader>
<DialogTitle>{t('common:header.feedback.title')}</DialogTitle>
<DialogDescription>{t('common:header.feedback.description')}</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="space-y-4">
<div className="space-y-1">
<Label htmlFor="feedback-form-name">{t('common:header.feedback.name')}</Label>
<Input
id="feedback-form-name"
value={name}
onChange={(e) => setName(e.currentTarget.value)}
className="col-span-3"
/>
</div>
<div className="space-y-1">
<Label htmlFor="feedback-form-feedback">{t('common:header.feedback.feedback')}</Label>
<Textarea
id="feedback-form-feedback"
value={feedback}
onChange={(e) => setFeedback(e.currentTarget.value)}
className="col-span-3"
// @ts-expect-error fieldSizing is a newer CSS property
style={{ fieldSizing: 'content' }}
required
/>
</div>
</div>
</div>
<DialogFooter>
<Button type="submit">{t('common:header.feedback.submit')}</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<Button variant="outline" size="icon" className="h-6 w-6 sm:h-8 sm:w-8" asChild>
<a href="https://discord.gg/YQ7negGTUK" target="_blank" rel="noopener noreferrer" title="ใ‚ตใƒใƒผใƒˆDiscord">
<a
href="https://discord.gg/YQ7negGTUK"
target="_blank"
rel="noopener noreferrer"
title={t('common:header.discord-button')}
>
<IconBrandDiscord className="h-4 w-5 sm:h-5 sm:w-5" />
</a>
</Button>
<Button variant="outline" size="icon" className="h-6 w-6 sm:h-8 sm:w-8" asChild title="ไฝœ่€…ใฎX (Twitter)">
<Button
variant="outline"
size="icon"
className="h-6 w-6 sm:h-8 sm:w-8"
asChild
title={t('common:header.twitter-button')}
>
<a href="https://twitter.com/__cp20__" target="_blank" rel="noopener noreferrer">
<IconBrandX className="h-4 w-5 sm:h-5 sm:w-5" />
</a>
Expand Down
35 changes: 35 additions & 0 deletions src/shared/lib/webhook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const webhookUrl = process.env.NEXT_PUBLIC_DISCORD_WEBHOOK_URL as string;

type Feedback = {
name?: string;
feedback: string;
};

export const sendFeedback = async (feedback: Feedback) => {
const body = {
content: 'ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏใŒๅฑŠใใพใ—ใŸ',
embeds: [
{
title: 'ใƒ•ใ‚ฃใƒผใƒ‰ใƒใƒƒใ‚ฏ',
description: feedback.feedback,
fields: [
{
name: 'ๅๅ‰',
value: feedback.name || 'ๆœชๅ…ฅๅŠ›',
},
],
},
],
};
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});

if (!response.ok) {
throw new Error('Failed to send feedback');
}
};

0 comments on commit 3699366

Please sign in to comment.